Skip to content
This repository has been archived by the owner on Jul 12, 2019. It is now read-only.

Feat/test parser #11

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
"@commitlint/config-conventional": "^7.1.2",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"chai": "^4.2.0",
"husky": "^1.3.1",
"jest": "^23.6.0",
"jest-circus": "^23.6.0",
"jest-junit": "^5.2.0",
"jsdom": "^13.2.0",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"rimraf": "^2.6.3",
Expand Down Expand Up @@ -84,6 +86,7 @@
"setupFiles": [
"./tasks/jest/setup.js"
],
"setupTestFrameworkScriptFile": "./tasks/jest/chai.js",
"testMatch": [
"<rootDir>/**/__tests__/**/*.js?(x)",
"<rootDir>/**/?(*.)(spec|test).js?(x)",
Expand All @@ -99,6 +102,7 @@
"/cjs/",
"/dist/",
"/es/",
"/fixtures/",
"/lib/"
],
"transformIgnorePatterns": [
Expand Down
20 changes: 20 additions & 0 deletions src/__tests__/fixtures/div--variant-one.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Div: Variant One: Preview Layout</title>
</head>
<body>
<header role="banner">
<a href="#main-content" class="wh-u-a11yFocusable">Skip to Content</a>
</header>

<main role="main" id="main-content" style="min-height: 100vh">

<div class="bx--meow bx--meow--variant-one"><span>Meow unique text</span></div>

</main>

</body>
</html>

77 changes: 77 additions & 0 deletions src/__tests__/fixtures/div-requirements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @fileOverview Exporable tests that match the Feature/Scenario pairs in div.feature
*/

/**
* Div user test configurations
* @typedef {object} divTestConfig
* @property {object} content - contains content for component
* @property {string} content.text - text content for component
* @property {divElements} selectors - Selectors to grab each element for a div component
*/
const defaults = {
content: {
text: 'Div Text',
},
selectors: {
root: 'div',
content: 'span',
},
};

/**
* Grabs Div component elements from a DOM
* @param {DocumentFragment} docFragment - JavaScript document or JSDOM fragment
* @param {divElements} selectors
* @returns {object} elements gathered from the selectors object
*/
const getDiv = (docFragment, selectors) => {
const div = docFragment.querySelector(selectors.root);
const content = div.querySelector(selectors.content);

return {
div,
content,
};
};

/**
* Very basic test for a standard div. These ensure that the Scenario "Standard div"
* has tests for each scenario and it's `then`s
* @type {scenario-test-object}
*/
const standardDiv = {
scenario: 'Standard div',
getActual: (docFragment = document) =>
new Promise(resolve => {
const component = getDiv(docFragment, defaults.selectors);

resolve({
html: component.div.innerHTML,
content: component.content.textContent.trim(),
classes: component.div.classList.value,
});
}),
comparison: actual => {
// see ./div.feature file for Scenario "Standard div"
// these tests conform to the `Then...` section
expect(actual.html, "I find said div's content").to.not.be.empty;
expect(actual.content, 'said content includes text').to.not.be.empty;
expect(actual.content, "said div's content is meaningful").to.not.equal(
defaults.content.text
);
},
};

/**
* Returns a set of test objects
* @return {scenario-test-object[]} test objects for use in `testRunner`
*/
export const divTests = () => {
return [
{
feature: 'Div is understandable and unique', // matches the `Feature` name in div.feature
tests: [standardDiv],
},
];
};
11 changes: 11 additions & 0 deletions src/__tests__/fixtures/div.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### User stories
Feature: Div is understandable and unique
As a user
I want to be able to access and understand a div's purpose
So that I can successfully understand a div's content

Scenario: Standard div
When I access said div
Then I find said div's content
And said content includes text
And said div's content is meaningful
53 changes: 53 additions & 0 deletions src/__tests__/test-parser-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright IBM Corp. 2018, 2019
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
import * as fs from 'fs';
import * as path from 'path';
import { JSDOM } from 'jsdom';
import { TestParser, Itter, GetDomFragment } from '../test-parser';
import { divTests } from './fixtures/div-requirements';

const DIV_HTML_TO_TEST = path.join(
__dirname,
'./fixtures/div--variant-one.html'
);

/**
* Variable representing a HTML of a component. Includes _entire_ HTML document.
* @type {string}
*/
const divHTML = fs.readFileSync(DIV_HTML_TO_TEST, 'utf8');

/**
* Options when creating the window/document from the HTML that's being tested
* @type {object}
*/
const JSDOMoptions = { resources: 'usable' };

/**
* Adjusts global window and document to be from the HTML we're gonna test
*/
const { window } = new JSDOM(divHTML, JSDOMoptions);
global.document = window.document;

describe('TestParser', () => {
describe('All functions exist', () => {
it('must have functions', () => {
expect(TestParser).to.exist;
expect(TestParser).to.be.a('function');
expect(Itter).to.exist;
expect(Itter).to.be.a('function');
expect(GetDomFragment).to.exist;
expect(GetDomFragment).to.be.a('function');
});
});

describe('Runs fixture requirement tests', () => {
TestParser(divTests(), document);
});
});
111 changes: 111 additions & 0 deletions src/test-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* global it, describe, before, beforeEach */

/**
* Scenario test object structure
* @typedef {object} scenario-test-object
* @property {string} scenario - Scenario name
* @property {function} [given] - Given statement that determines under what conditions a test should run
* @property {function} [set] - Returns an array of DocumentFragments that the test is executed on
* @property {function} [inherit] - Returns an array of tests from an inherited component.
* Feature document denotes these tests with the @inheritance tag
* @property {function} [getDomFragment] - Allows developers the ability to get sub-fragment/child docFragment object
* such as when inheriting
* @property {function} getActual - Returns an object of data points that were gathered by reading or interacting
* with elements within a DocumentFragment. Native JavaScript that can be executed on any browser
* @property {function} comparison - Tests the object from the corresponding `getActual` function for verification.
* Has no relationship with the DOM.
*/

/**
* Feature test object structure
* @typedef {object} feature-test-object
* @property {string} feature -
* @property {function} [set] -
* @property {scenario-test-object[]} tests -
*/

/**
* Tests whether a scenario-test-object has a custom function for getting the test fragment or
* returns the default DOM object
* @param {scenario-test-object} test - Scenario being tested
* @param {DocumentFragment} docFragment - Javascript DOM fragment
* @returns {DocumentFragment}
*/
const getDomFragment = (test, docFragment) => {
if (test && test.getDomFragment && typeof test.getDomFragment === 'function')
return test.getDomFragment(docFragment);
return docFragment;
};

/**
* Karma implementation of a mocha `it` function that is used to test
* an individual scenario. This function is custom per test-runner used.
* @param {scenario-test-object} test - Scenario being tested
* @param {DocumentFragment} docFragment - fragment being tested
*/
const itter = (test, docFragment) => {
it(test.scenario, () => test.getActual(docFragment).then(test.comparison));
};

/**
* Loops through an array of feature-test-objects and creates `describe` statements per feature or set item.
* This implementation was build for Karma/Mocha but does not have any Karma specific features.
*
* @param {feature-test-object[]} tests - test runner called from a specific component/application page
* @param {DocumentFragment} docFragment - default docFragment object
* @param {integer} [index] - current index when running a `set`
*/
const testParser = (tests, docFragment, index) => {
if (!Array.isArray(tests)) return;
tests.forEach(test => {
if (test.feature) {
if (typeof test.set === 'function') {
const docFragmentArray = test.set(docFragment);
describe(test.feature, () => {
if (typeof test.before === 'function') before(test.before);
if (typeof test.beforeEach === 'function')
beforeEach(test.beforeEach);
for (let i = 0; i < docFragmentArray.length; i++) {
describe(`Set index: ${i}`, () => {
testParser(test.tests, docFragmentArray[i], i);
});
}
});
} else if (Array.isArray(test.tests)) {
describe(test.feature, () => {
if (typeof test.before === 'function') before(test.before);
if (typeof test.beforeEach === 'function')
beforeEach(test.beforeEach);
testParser(test.tests, docFragment);
});
}
} else if (test.scenario) {
if (typeof test.set === 'function') {
const docFragmentArray = test.set(docFragment);
for (let i = 0; i < docFragmentArray.length; i++) {
describe(`Set index: ${i}`, () => {
if (typeof test.given === 'function') {
if (test.given(docFragment, index))
itter(test, docFragmentArray[i]);
} else {
itter(test, docFragmentArray[i]);
}
});
}
} else if (Array.isArray(test.inherit)) {
describe(test.scenario, () => {
testParser(test.inherit, getDomFragment(test, docFragment));
});
} else if (typeof test.given === 'function') {
if (test.given(docFragment, index))
itter(test, getDomFragment(test, docFragment));
} else {
itter(test, getDomFragment(test, docFragment));
}
}
});
};

export const TestParser = testParser;
export const Itter = itter;
export const GetDomFragment = getDomFragment;
46 changes: 46 additions & 0 deletions tasks/jest/chai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright IBM Corp. 2018, 2019
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';
import chai from 'chai';

// adds `chai` assertions to Jest's `expect` assertions,
// found here: https://gist.github.com/pahund/3abcc5212431cef3dae455d5285b7bd7
// Make sure chai and jasmine ".not" play nice together
const originalNot = Object.getOwnPropertyDescriptor(
chai.Assertion.prototype,
'not'
).get;
Object.defineProperty(chai.Assertion.prototype, 'not', {
get() {
Object.assign(this, this.assignedNot);
return originalNot.apply(this);
},
set(newNot) {
this.assignedNot = newNot;
return newNot;
},
});

// Combine both jest and chai matchers on expect
const jestExpect = global.expect;

/**
* _Should_ combine Jest expect with Chai expect, but Jest's aren't working
* @todo get this working -or- choose one or the other assertion lib
*/
global.expect = actual => {
const originalMatchers = jestExpect(actual);
const chaiMatchers = chai.expect(actual);
const combinedMatchers = Object.assign(chaiMatchers, originalMatchers);
return combinedMatchers;
};

// global.expect is _not_ picking up the Jest expect assertions
Object.keys(jestExpect).forEach(key => {
global.expect[key] = jestExpect[key];
});
Loading