Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@expressen/tallahassee

Package Overview
Dependencies
Maintainers
4
Versions
207
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@expressen/tallahassee - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

lib/Element.js

6

index.js
"use strict";
const Document = require("./lib/Document");
const Fetch = require("./lib/Fetch");
const supertest = require("supertest");
const Window = require("./lib/Window");
const {compile} = require("./lib/Compiler");
const {Document, Fetch, Window, Compiler} = require("./lib");
const {compile} = Compiler;

@@ -9,0 +7,0 @@ module.exports = Tallahassee;

@@ -32,3 +32,3 @@ "use strict";

function Compiler(assetPatterns) {
function Compiler(assetPatterns, plugins = [], presets = ["node8"] ) {
if (registered) return;

@@ -40,4 +40,4 @@

ignore,
plugins: [],
presets: ["node8"],
plugins,
presets,
});

@@ -44,0 +44,0 @@

@@ -6,4 +6,4 @@ "use strict";

const getLocation = require("./getLocation");
const Url = require("url");
const vm = require("vm");
const Element = require("./Element");
const {EventEmitter} = require("events");

@@ -16,19 +16,34 @@

let cookieHeader = getHeaders(resp).cookie || "";
const setCookies = {};
const $ = cheerio.load(resp.text, {decodeEntities: false});
const loaded = [];
const doc = MockElement($);
doc._getElement = getElement;
doc.$ = $;
doc.getElementById = getElementById;
const document = Object.assign(new EventEmitter(), {
createElement,
getElementById,
getElementsByTagName,
getElementsByClassName,
location,
textContent: null,
$,
_getElement: getElement,
});
Object.defineProperty(doc, "head", {
Object.defineProperty(document, "head", {
get: getHead
});
Object.defineProperty(doc, "body", {
Object.defineProperty(document, "body", {
get: getBody
});
Object.defineProperty(doc, "cookie", {
Object.defineProperty(document, "firstElementChild", {
get: () => getElement($("html"))
});
Object.defineProperty(document, "firstChild", {
get: () => getElement($("> :first-child"))
});
Object.defineProperty(document, "cookie", {
get: () => cookieHeader,

@@ -43,4 +58,11 @@ set: (value) => {

return doc;
Object.defineProperty(document, "title", {
get: () => getElement($("head > title")).textContent,
set: (value) => {
getElement($("head > title")).textContent = value;
}
});
return document;
function getHead() {

@@ -55,3 +77,3 @@ return getElement($("head"));

function getElement($elm) {
if ($elm === $) return doc;
if ($elm === $) return document;

@@ -63,3 +85,3 @@ let mockElement = loaded.find((mockedElm) => mockedElm.$elm[0] === $elm[0]);

mockElement = MockElement($elm);
mockElement = Element(document, $elm);
loaded.push(mockElement);

@@ -76,321 +98,35 @@ return mockElement;

function MockElement($elm) {
const isDocument = $elm === $;
const tagName = (($elm[0] && $elm[0].name) || "").toLowerCase();
let top = 761, bottom = top + 760 * 2, height = bottom - top;
const emitter = new EventEmitter();
const listenEvents = [];
function getElementsByTagName(query) {
return $(`${query}`).map((idx, elm) => getElement(document.$(elm))).toArray();
}
const classList = getClassList();
const element = {
$elm,
appendChild,
classList,
click,
dispatchEvent,
getAttribute,
getElementsByClassName,
getElementsByTagName,
insertAdjacentHTML,
getBoundingClientRect,
setBoundingClientRect,
addEventListener,
style: {},
createElement,
removeChild,
runScripts,
setAttribute,
removeAttribute,
_emitter: emitter,
_listenEvents: listenEvents
};
function getElementsByClassName(className) {
return $(`.${className}`).map((idx, elm) => getElement(document.$(elm))).toArray();
}
function addEventListener(eventName, fn) {
listenEvents.push(eventName);
emitter.on(eventName, fn.bind(this));
}
function createElement(elementTagName) {
const elementDOM = Document({ text: `<${elementTagName}></${elementTagName}>` });
return elementDOM.getElementsByTagName(elementTagName)[0];
}
Object.defineProperty(element, "firstElementChild", {
get: getElementFirstChild
});
function parseSetCookie(cookie) {
const setCookiePattern = /^(\w+)=(.*?)(;(.*?))?$/g;
const parsed = {};
if (!/;$/.test(cookie)) cookie = `${cookie};`;
Object.defineProperty(element, "lastElementChild", {
get: getElementLastChild
cookie.trim().replace(setCookiePattern, (match, name, value, settings) => {
parsed.cookie = `${name}=${value};`;
parsed.name = name;
parsed.value = decodeURIComponent(value);
Object.assign(parsed, parseSettings(settings));
});
Object.defineProperty(element, "dataset", {
get: getDataset
});
Object.defineProperty(element, "tagName", {
get: () => tagName ? tagName.toUpperCase() : undefined
});
Object.defineProperty(element, "children", {
get: getChildren
});
Object.defineProperty(element, "parentElement", {
get: () => getElement($elm.parent())
});
Object.defineProperty(element, "innerText", {
get: () => $elm.html()
});
Object.defineProperty(element, "href", {
get: () => {
const rel = getAttribute("href");
if (!rel) return;
if (!rel.startsWith("/")) return rel;
return Url.format(Object.assign({}, location, {path: rel}));
}
});
Object.defineProperty(element, "value", {
get: () => {
return getAttribute("value");
},
set: (value) => {
return setAttribute("value", value);
}
});
Object.defineProperty(element, "textContent", {
get: () => {
if (isDocument) return null;
return tagName === "script" ? $elm.html() : $elm.text();
},
set: (value) => {
if (isDocument) return;
return tagName === "script" ? $elm.html(value) : $elm.text(value);
}
});
Object.defineProperty(element, "checked", {
get: () => {
return getAttribute("checked") === "checked";
},
set: (value) => {
const oldValue = (getAttribute("checked") === "checked");
uncheckRadioButtons();
if (value) {
setAttribute("checked", "checked");
if (!oldValue) {
emitter.emit("change", emptyEvent());
}
}
}
});
return element;
function getElementFirstChild() {
return getElement(find("> :first-child"));
if (parsed.name) {
setCookies[parsed.name] = parsed;
}
function getElementLastChild() {
return getElement(find("> :last-child"));
}
function toMockElement(idx, elm) {
return getElement($(elm));
}
function getElementsByClassName(query) {
return find(`.${query}`).map(toMockElement).toArray();
}
function getElementsByTagName(query) {
return find(`${query}`).map((idx, elm) => getElement($(elm))).toArray();
}
function appendChild(childElement) {
$elm.append(childElement.$elm.parent().html());
if (childElement.$elm[0].tagName === "script") {
vm.runInThisContext(childElement.innerText);
}
}
function click() {
emitter.emit("click", {
preventDefault: () => {},
stopPropagation: () => {}
});
}
function dispatchEvent(...args) {
emitter.emit(...args);
}
function getAttribute(name) {
return $elm.attr(name);
}
function removeChild(childElement) {
childElement.$elm.remove();
}
function find(selector) {
if (isDocument) {
return $(selector);
}
return $elm.find(selector);
}
function getDataset() {
if (!$elm || !$elm[0] || !$elm[0].attribs) return {};
const {attribs} = $elm[0];
return Object.keys(attribs).reduce((acc, key) => {
if (key.startsWith("data-")) {
acc[key.replace(/^data-/, "").replace(/-(\w)/g, (a, b) => b.toUpperCase())] = attribs[key];
}
return acc;
}, {});
}
function getChildren() {
if (!$elm) return [];
return $elm.children().map(toMockElement).toArray();
}
function insertAdjacentHTML(position, markup) {
$elm.append(markup);
}
function setBoundingClientRect(setTop, setBottom) {
top = setTop;
if (setBottom === undefined) {
bottom = top + height;
} else {
bottom = setBottom;
height = bottom - top;
}
}
function getBoundingClientRect() {
return {
top,
bottom,
height
};
}
function createElement(elementTagName) {
const elementDOM = Document({ text: `<${elementTagName}></${elementTagName}>` });
return elementDOM.getElementsByTagName(elementTagName)[0];
}
function runScripts() {
$("script").each((idx, elm) => {
const scriptBody = $(elm).html();
if (scriptBody) vm.runInThisContext(scriptBody);
});
}
function setAttribute(name, val) {
$elm.attr(name, val);
}
function removeAttribute(name) {
$elm.removeAttr(name);
}
function uncheckRadioButtons() {
if ($elm.attr("type") !== "radio") return;
const name = $elm.attr("name");
const $form = $elm.closest("form");
if ($form && $form.length) {
return $form.find(`input[type="radio"][name="${name}"]`).removeAttr("checked");
}
$(`input[type="radio"][name="${name}"]`).removeAttr("checked");
}
function emptyEvent() {
let defaultPrevented;
const event = {
preventDefault,
stopPropagation: () => {}
};
Object.defineProperty(event, "defaultPrevented", {
get: () => defaultPrevented
});
return event;
function preventDefault() {
defaultPrevented = true;
}
}
function getClassList() {
if (!$elm.attr) return;
const classListApi = {
contains(className) {
return $elm.hasClass(className);
},
add(...classNames) {
$elm.addClass(classNames.join(" "));
},
remove(...classNames) {
$elm.removeClass(classNames.join(" "));
},
toggle(className, force) {
const hasClass = $elm.hasClass(className);
if (force === undefined) {
$elm.toggleClass(className);
return !hasClass;
}
if (force) {
$elm.addClass(className);
} else {
$elm.removeClass(className);
}
return !hasClass;
}
};
Object.defineProperty(classListApi, "_classes", {
get: getClassArray
});
return classListApi;
function getClassArray() {
return ($elm.attr("class") || "").split(" ");
}
}
return parsed;
}
}
function parseSetCookie(cookie) {
const setCookiePattern = /^(\w+)=(.*?)(;(.*?))?$/g;
const parsed = {};
if (!/;$/.test(cookie)) cookie = `${cookie};`;
cookie.trim().replace(setCookiePattern, (match, name, value, settings) => {
parsed.cookie = `${name}=${value};`;
parsed.name = name;
parsed.value = decodeURIComponent(value);
Object.assign(parsed, parseSettings(settings));
});
if (parsed.name) {
this.setCookies[parsed.name] = parsed;
}
return parsed;
}
function parseSettings(settings) {

@@ -407,2 +143,1 @@ const settingsPattern = /(\w+)=(.*?)(;|$)/g;

}

@@ -6,4 +6,2 @@ "use strict";

function ElementScroller(browser, stackedElementsFn) {
const innerHeight = browser.window.innerHeight;
stackedElementsFn = stackedElementsFn || getArticleElements;

@@ -18,13 +16,12 @@

const elements = stackedElementsFn();
const index = elements.indexOf(element);
if (!element) return;
const {top: previousTop} = element.getBoundingClientRect();
const diff = offset - previousTop;
for (let i = 0; i < elements.length; i++) {
const elm = elements[i];
if (i < index) {
elm.setBoundingClientRect(-99999);
} else if (i === index) {
elm.setBoundingClientRect(0 + offset);
} else {
elm.setBoundingClientRect(1000);
}
const {top} = elm.getBoundingClientRect();
elm.setBoundingClientRect(top + diff);
}

@@ -36,23 +33,10 @@

function scrollToBottomOfElement(element, offset = 0) {
const elements = stackedElementsFn();
const index = elements.indexOf(element);
for (let i = 0; i < elements.length; i++) {
const elm = elements[i];
if (i < index) {
elm.setBoundingClientRect(-99999);
} else if (i === index) {
const {height} = elm.getBoundingClientRect();
elm.setBoundingClientRect(innerHeight - height + offset);
} else {
elm.setBoundingClientRect(innerHeight + 1000);
}
}
browser.window.scroll();
const {height} = element.getBoundingClientRect();
const offsetFromBottom = browser.window.innerHeight - height;
return scrollToTopOfElement(element, offsetFromBottom + offset);
}
function getArticleElements() {
return browser.document.getElementsByClassName("article");
return browser.document.body.getElementsByTagName("article");
}
}

@@ -10,3 +10,6 @@ "use strict";

for (const name in reqHeader) {
headers[name.toLowerCase()] = reqHeader[name];
const lowerName = name.toLowerCase();
let val = reqHeader[name];
if (lowerName === "cookie") val = cleanCookie(val);
headers[lowerName] = val;
}

@@ -16,1 +19,7 @@

};
function cleanCookie(cookie) {
if (!cookie) return "";
if (!/;$/.test(cookie)) return `${cookie};`;
return cookie;
}

@@ -6,3 +6,3 @@ "use strict";

module.exports = function Window(resp, windowObjects, innerHeight = 760) {
module.exports = function Window(resp, windowObjects, innerWidth = 760, innerHeight = 760) {
const location = getLocation(resp.request);

@@ -12,12 +12,15 @@

const window = Object.assign({
innerHeight,
location,
_resize: resizeWindow,
addEventListener,
clearTimeout: () => {},
dispatchEvent,
scroll: dispatchEvent.bind(null, "scroll"),
setTimeout: (fn, ms, ...args) => fn(...args),
removeEventListener,
history: {
replaceState
},
innerHeight,
innerWidth,
location,
removeEventListener,
scroll: dispatchEvent.bind(null, "scroll"),
setTimeout: (fn, ms, ...args) => fn(...args),
}, windowObjects);

@@ -42,2 +45,12 @@

}
function resizeWindow(newInnerWidth, newInnerHeight) {
if (newInnerWidth !== undefined) {
window.innerWidth = newInnerWidth;
}
if (newInnerHeight !== undefined) {
window.innerHeight = newInnerHeight;
}
window.dispatchEvent("resize");
}
};
{
"name": "@expressen/tallahassee",
"version": "0.2.0",
"version": "0.3.0",
"description": "Expressen client testing framework",

@@ -15,3 +15,6 @@ "main": "index.js",

"headless",
"browser"
"browser",
"fake",
"mock",
"IntersectionObserver"
],

@@ -33,6 +36,6 @@ "author": "AB Kvällstidningen Expressen",

"chai-as-promised": "^7.1.1",
"eslint": "^4.12.1",
"eslint": "^4.16.0",
"express": "^4.16.2",
"mocha": "^4.0.1",
"nock": "^9.1.4"
"mocha": "^5.0.0",
"nock": "^9.1.6"
},

@@ -39,0 +42,0 @@ "files": [

@@ -6,8 +6,14 @@ Tallahassee

[![Build Status](https://travis-ci.org/ExpressenAB/tallahassee.svg?branch=master)](https://travis-ci.org/ExpressenAB/tallahassee)
[![Build Status](https://travis-ci.org/ExpressenAB/tallahassee.svg?branch=master)](https://travis-ci.org/ExpressenAB/tallahassee)[![dependencies Status](https://david-dm.org/ExpressenAB/tallahassee/status.svg)](https://david-dm.org/ExpressenAB/tallahassee)
Test your client scripts in a headless browser.
Example:
# Introduction
Supports just about everything except `querySelectorAll()` which we don´t want developers to use.
- IntersectionObserver? Yes, check [here](#intersectionobserver)
# Example:
```javascript

@@ -48,5 +54,4 @@ "use strict";

expect(browser.document.cookie).to.equal("_ga=1");
expect(browser.document.cookie).to.equal("_ga=1;");
expect(browser.document.getElementsByClassName("set-by-js")).to.have.length(1);
});

@@ -65,1 +70,49 @@

```
# Api
## IntersectionObserver
```javascript
"use strict";
const app = require("../app/express-js-app");
const Browser = require("@expressen/tallahassee");
const {Compiler, IntersectionObserver, ElementScroller} = require("@expressen/tallahassee/lib");
describe("IntersectionObserver", () => {
before(() => {
Compiler.Compiler([/assets\/scripts/]);
});
it("observes elements", async () => {
const browser = await Browser(app).navigateTo("/", {
Cookie: "_ga=1"
});
const intersectionObserver = browser.window.IntersectionObserver = IntersectionObserver(browser);
require("../app/assets/scripts/main");
expect(intersectionObserver._getObserved()).to.have.length(1);
});
it("listens to window scroll", async () => {
const browser = await Browser(app).navigateTo("/", {
Cookie: "_ga=1"
});
browser.window.IntersectionObserver = IntersectionObserver(browser);
require("../app/assets/scripts/main");
const scroller = ElementScroller(browser, () => {
return browser.document.getElementsByClassName("lazy-load");
});
scroller.scrollToTopOfElement(browser.document.getElementsByClassName("lazy-load")[0]);
expect(browser.document.getElementsByClassName("lazy-load").length).to.equal(0);
expect(browser.document.getElementsByTagName("img")[1].src).to.be.ok;
});
});
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc