mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat: support beforeEach and afterEach (#60921)
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
committed by
GitHub
parent
38cd1727e4
commit
2a7b220a4f
@@ -300,6 +300,7 @@ exports.createSchemaCustomization = ({ actions }) => {
|
||||
challengeFiles: [FileContents]
|
||||
chapter: String
|
||||
explanation: String
|
||||
hooks: Hooks
|
||||
notes: String
|
||||
url: String
|
||||
assignments: [String]
|
||||
@@ -379,6 +380,11 @@ exports.createSchemaCustomization = ({ actions }) => {
|
||||
distractors: [String]
|
||||
answer: String
|
||||
}
|
||||
type Hooks {
|
||||
beforeEach: String
|
||||
afterEach: String
|
||||
beforeAll: String
|
||||
}
|
||||
`;
|
||||
createTypes(typeDefs);
|
||||
};
|
||||
|
||||
@@ -192,7 +192,7 @@ export type ChallengeNode = {
|
||||
head: string[];
|
||||
hasEditableBoundaries: boolean;
|
||||
helpCategory: string;
|
||||
hooks?: { beforeAll: string };
|
||||
hooks?: Hooks;
|
||||
id: string;
|
||||
instructions: string;
|
||||
isComingSoon: boolean;
|
||||
@@ -240,6 +240,12 @@ export type ChallengeNode = {
|
||||
};
|
||||
};
|
||||
|
||||
export interface Hooks {
|
||||
beforeAll?: string;
|
||||
beforeEach?: string;
|
||||
afterEach?: string;
|
||||
}
|
||||
|
||||
type Quiz = {
|
||||
questions: QuizQuestion[];
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
ChallengeFiles,
|
||||
ChallengeMeta,
|
||||
ChallengeNode,
|
||||
Hooks,
|
||||
ResizeProps,
|
||||
SavedChallenge,
|
||||
SavedChallengeFiles,
|
||||
@@ -110,7 +111,7 @@ interface ShowClassicProps extends Pick<PreviewProps, 'previewMounted'> {
|
||||
challengeFiles: ChallengeFiles;
|
||||
initConsole: (arg0: string) => void;
|
||||
initTests: (tests: Test[]) => void;
|
||||
initHooks: (hooks?: { beforeAll: string }) => void;
|
||||
initHooks: (hooks?: Hooks) => void;
|
||||
initVisibleEditors: () => void;
|
||||
isChallengeCompleted: boolean;
|
||||
output: string;
|
||||
@@ -579,6 +580,8 @@ export const query = graphql`
|
||||
forumTopicId
|
||||
hooks {
|
||||
beforeAll
|
||||
beforeEach
|
||||
afterEach
|
||||
}
|
||||
fields {
|
||||
blockName
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
FrameDocument,
|
||||
PythonDocument
|
||||
} from '../../../../../tools/client-plugins/browser-scripts';
|
||||
import { Hooks } from '../../../redux/prop-types';
|
||||
|
||||
export const helperVersion = _helperVersion;
|
||||
|
||||
@@ -28,10 +29,6 @@ export interface Source {
|
||||
editableContents: string;
|
||||
}
|
||||
|
||||
interface Hooks {
|
||||
beforeAll?: string;
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
window?: Window &
|
||||
typeof globalThis & {
|
||||
|
||||
@@ -18,6 +18,12 @@ Using `let` or `const`, declare a global variable named `myGlobal` outside of an
|
||||
|
||||
Inside function `fun1`, assign `5` to `oopsGlobal` ***without*** using the `var`, `let` or `const` keywords.
|
||||
|
||||
# --before-each--
|
||||
|
||||
```js
|
||||
var oopsGlobal;
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
`myGlobal` should be defined
|
||||
@@ -41,45 +47,12 @@ assert(/(let|const)\s+myGlobal/.test(__helpers.removeJSComments(code)));
|
||||
`oopsGlobal` should be a global variable and have a value of `5`
|
||||
|
||||
```js
|
||||
assert(typeof oopsGlobal != 'undefined' && oopsGlobal === 5);
|
||||
fun1();
|
||||
assert(typeof oopsGlobal != 'undefined');
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```js
|
||||
var logOutput = "";
|
||||
var originalConsole = console
|
||||
function capture() {
|
||||
var nativeLog = console.log;
|
||||
console.log = function (message) {
|
||||
logOutput = message;
|
||||
if(nativeLog.apply) {
|
||||
nativeLog.apply(originalConsole, arguments);
|
||||
} else {
|
||||
var nativeMsg = Array.prototype.slice.apply(arguments).join(' ');
|
||||
nativeLog(nativeMsg);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function uncapture() {
|
||||
console.log = originalConsole.log;
|
||||
}
|
||||
var oopsGlobal;
|
||||
capture();
|
||||
```
|
||||
|
||||
## --after-user-code--
|
||||
|
||||
```js
|
||||
fun1();
|
||||
fun2();
|
||||
uncapture();
|
||||
(function() { return logOutput || "console.log never called"; })();
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
|
||||
@@ -23,12 +23,19 @@ Fulfill the user stories below and get all the tests to pass to complete the lab
|
||||
1. You should log the `codingFact` to the console a third time.
|
||||
1. You should log `"It was fun sharing these facts with you. Goodbye! - (botName) from (botLocation)."` to the console as a farewell statement from the bot.
|
||||
|
||||
# --before-each--
|
||||
|
||||
```js
|
||||
const spy = __helpers.spyOn(console, 'log');
|
||||
const getLogs = () => spy.calls.map(call => call?.[0]);
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
You should log `"Hello! I'm your coding fun fact guide!"` to the console.
|
||||
|
||||
```js
|
||||
assert.equal(output[0], "Hello! I'm your coding fun fact guide!")
|
||||
assert.equal(getLogs()[0], "Hello! I'm your coding fun fact guide!")
|
||||
```
|
||||
|
||||
You should declare a `botName` variable. Double check for any spelling or casing errors.
|
||||
@@ -71,7 +78,7 @@ You should log to the console `"My name is (botName) and I live on (botLocation)
|
||||
|
||||
```js
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
assert.equal(output[1], `My name is ${botName} and I live on ${botLocation}.`)
|
||||
assert.equal(getLogs()[1], `My name is ${botName} and I live on ${botLocation}.`)
|
||||
assert.match(codeWithoutComments, /is ("|')\s*\+\s*botName\s*\+\s*("|') and I live on \2\s*\+\s*botLocation\s*\+\s*('|")\./)
|
||||
```
|
||||
|
||||
@@ -79,7 +86,7 @@ You should log to the console `"My favorite programming language is (favoriteLan
|
||||
|
||||
```js
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
assert.equal(output[2], `My favorite programming language is ${favoriteLanguage}.`)
|
||||
assert.equal(getLogs()[2], `My favorite programming language is ${favoriteLanguage}.`)
|
||||
assert.match(codeWithoutComments, /language is ('|")\s*\+\s*favoriteLanguage\s*\+\s*('|")\./);
|
||||
```
|
||||
|
||||
@@ -106,7 +113,7 @@ You should log `codingFact` to the console.
|
||||
```js
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
const loggingCodingFacts = codeWithoutComments.match(/console\.log\(\s*codingFact\s*\)/g)
|
||||
assert.include(output[3], favoriteLanguage);
|
||||
assert.include(getLogs()[3], favoriteLanguage);
|
||||
assert.isAtLeast(loggingCodingFacts.length, 1);
|
||||
```
|
||||
|
||||
@@ -116,8 +123,8 @@ You should assign a new value to `codingFact` that also contains `favoriteLangua
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
const loggingCodingFacts = codeWithoutComments.match(/console\.log\(\s*codingFact\s*\)/g)
|
||||
const [first, second, third] = codeWithoutComments.match(/(let )?\s*codingFact\s*=\s*(("|')?.+?\2?\s*\+\s*|favoriteLanguage\s*\+\s*(("|')?.+?\2?))/g);
|
||||
assert.include(output[4], favoriteLanguage);
|
||||
assert.notEqual(output[4], output[3]);
|
||||
assert.include(getLogs()[4], favoriteLanguage);
|
||||
assert.notEqual(getLogs()[4], getLogs()[3]);
|
||||
assert.isAtLeast(loggingCodingFacts.length, 2);
|
||||
assert.exists(second);
|
||||
assert.isNotEmpty(codingFact);
|
||||
@@ -129,9 +136,9 @@ You should assign a value to `codingFact` for the third time that also contains
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
const loggingCodingFacts = codeWithoutComments.match(/console\.log\(\s*codingFact\s*\)/g)
|
||||
const [first, second, third] = codeWithoutComments.match(/(let )?\s*codingFact\s*=\s*(("|')?.+?\2?\s*\+\s*|favoriteLanguage\s*\+\s*(("|')?.+?\2?))/g);
|
||||
assert.include(output[5], favoriteLanguage);
|
||||
assert.notEqual(output[5], output[4]);
|
||||
assert.equal(output[5], codingFact);
|
||||
assert.include(getLogs()[5], favoriteLanguage);
|
||||
assert.notEqual(getLogs()[5], getLogs()[4]);
|
||||
assert.equal(getLogs()[5], codingFact);
|
||||
assert.lengthOf(loggingCodingFacts, 3);
|
||||
assert.exists(third);
|
||||
assert.isNotEmpty(codingFact);
|
||||
@@ -141,23 +148,12 @@ You should log to the console `"It was fun sharing these facts with you. Goodbye
|
||||
|
||||
```js
|
||||
const codeWithoutComments = __helpers.removeJSComments(code);
|
||||
assert.equal(output[6], `It was fun sharing these facts with you. Goodbye! - ${botName} from ${botLocation}.`);
|
||||
assert.equal(getLogs()[6], `It was fun sharing these facts with you. Goodbye! - ${botName} from ${botLocation}.`);
|
||||
assert.match(codeWithoutComments, /\. Goodbye! - ("|')\s*\+\s*botName\s*\+\s*('|") from \2\s*\+\s*botLocation\s*\+\s*("|')\./)
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --before-user-code--
|
||||
|
||||
```js
|
||||
const temp = console.log
|
||||
const output = []
|
||||
console.log = function (...args) {
|
||||
temp(...args)
|
||||
output.push(...args)
|
||||
}
|
||||
```
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```js
|
||||
|
||||
@@ -302,7 +302,9 @@ const schema = Joi.object()
|
||||
superOrder: Joi.number(),
|
||||
suborder: Joi.number(),
|
||||
hooks: Joi.object().keys({
|
||||
beforeAll: Joi.string().allow('')
|
||||
beforeAll: Joi.string().allow(''),
|
||||
beforeEach: Joi.string().allow(''),
|
||||
afterEach: Joi.string().allow('')
|
||||
}),
|
||||
tests: Joi.array()
|
||||
.items(
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --after-each--
|
||||
|
||||
```js
|
||||
// after each code
|
||||
function cleanup() {
|
||||
return 'cleaned up';
|
||||
}
|
||||
cleanup();
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --after-each--
|
||||
|
||||
gubbins
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --before-each--
|
||||
|
||||
gubbins
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --before-each--
|
||||
|
||||
```js
|
||||
// before each code
|
||||
function setup() {
|
||||
return 'initialized';
|
||||
}
|
||||
setup();
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --after-each--
|
||||
|
||||
```js
|
||||
// after each code
|
||||
function cleanup() {
|
||||
return 'cleaned up';
|
||||
}
|
||||
cleanup();
|
||||
```
|
||||
|
||||
gubbins
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --before-each--
|
||||
|
||||
```js
|
||||
// before each code
|
||||
function setup() {
|
||||
return 'initialized';
|
||||
}
|
||||
setup();
|
||||
```
|
||||
|
||||
gubbins
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --after-each--
|
||||
|
||||
```ts
|
||||
// after each code
|
||||
function cleanup() {
|
||||
return 'cleaned up';
|
||||
}
|
||||
cleanup();
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# --description--
|
||||
|
||||
Paragraph 1
|
||||
|
||||
```html
|
||||
code example
|
||||
```
|
||||
|
||||
# --before-each--
|
||||
|
||||
```ts
|
||||
// before each code
|
||||
function setup() {
|
||||
return 'initialized';
|
||||
}
|
||||
setup();
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
First hint
|
||||
|
||||
```js
|
||||
// test code
|
||||
```
|
||||
|
||||
Second hint with <code>code</code>
|
||||
|
||||
```js
|
||||
// more test code
|
||||
```
|
||||
|
||||
Third *hint* with <code>code</code> and `inline code`
|
||||
|
||||
```js
|
||||
// more test code
|
||||
if(let x of xs) {
|
||||
console.log(x);
|
||||
}
|
||||
```
|
||||
@@ -7,7 +7,7 @@ const addFillInTheBlank = require('./plugins/add-fill-in-the-blank');
|
||||
const addFrontmatter = require('./plugins/add-frontmatter');
|
||||
const addSeed = require('./plugins/add-seed');
|
||||
const addSolution = require('./plugins/add-solution');
|
||||
const addBeforeHook = require('./plugins/add-before-hook');
|
||||
const addHooks = require('./plugins/add-hooks');
|
||||
const addTests = require('./plugins/add-tests');
|
||||
const addText = require('./plugins/add-text');
|
||||
const addVideoQuestion = require('./plugins/add-video-question');
|
||||
@@ -53,7 +53,7 @@ const processor = unified()
|
||||
.use(addAssignment)
|
||||
.use(addScene)
|
||||
.use(addQuizzes)
|
||||
.use(addBeforeHook)
|
||||
.use(addHooks)
|
||||
.use(addTests)
|
||||
.use(addText, [
|
||||
'description',
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
const { getSection } = require('./utils/get-section');
|
||||
|
||||
function plugin() {
|
||||
return transformer;
|
||||
|
||||
function transformer(tree, file) {
|
||||
const section = getSection(tree, '--before-all--');
|
||||
|
||||
if (section.length === 0) return;
|
||||
if (section.length > 1)
|
||||
throw Error(
|
||||
'#--before-all-- section must only contain a single code block'
|
||||
);
|
||||
|
||||
const codeNode = section[0];
|
||||
|
||||
if (codeNode.type !== 'code')
|
||||
throw Error('#--before-all-- section must contain a code block');
|
||||
if (codeNode.lang !== 'javascript' && codeNode.lang !== 'js')
|
||||
throw Error('#--before-all-- hook must be written in JavaScript');
|
||||
|
||||
const beforeAll = getBeforeAll(codeNode);
|
||||
file.data.hooks = { beforeAll };
|
||||
}
|
||||
}
|
||||
|
||||
function getBeforeAll(codeNode) {
|
||||
const beforeAll = codeNode.value;
|
||||
|
||||
return beforeAll;
|
||||
}
|
||||
|
||||
module.exports = plugin;
|
||||
@@ -1,67 +0,0 @@
|
||||
const parseFixture = require('../__fixtures__/parse-fixture');
|
||||
|
||||
const addBeforeHook = require('./add-before-hook');
|
||||
|
||||
describe('add-before-hook plugin', () => {
|
||||
let withBeforeHookAST,
|
||||
withInvalidHookAST,
|
||||
withAnotherInvalidHookAST,
|
||||
withNonJSHookAST;
|
||||
|
||||
const plugin = addBeforeHook();
|
||||
let file = { data: {} };
|
||||
|
||||
beforeAll(async () => {
|
||||
withBeforeHookAST = await parseFixture('with-before-hook.md');
|
||||
withInvalidHookAST = await parseFixture('with-invalid-before-hook.md');
|
||||
withAnotherInvalidHookAST = await parseFixture(
|
||||
'with-another-invalid-before-hook.md'
|
||||
);
|
||||
withNonJSHookAST = await parseFixture('with-non-js-before-hook.md');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
file = { data: {} };
|
||||
});
|
||||
|
||||
it('returns a function', () => {
|
||||
expect(typeof plugin).toEqual('function');
|
||||
});
|
||||
|
||||
it('adds a `hooks` property to `file.data`', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect('hooks' in file.data).toBe(true);
|
||||
});
|
||||
|
||||
it('populates `hooks.beforeAll` with the contents of the code block', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect(file.data.hooks.beforeAll).toBe(`// before all code
|
||||
function foo() {
|
||||
return 'bar';
|
||||
}
|
||||
foo();`);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeAll section has more than one child', () => {
|
||||
expect(() => plugin(withInvalidHookAST, file)).toThrow(
|
||||
`#--before-all-- section must only contain a single code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeAll section does not contain a code block', () => {
|
||||
expect(() => plugin(withAnotherInvalidHookAST, file)).toThrow(
|
||||
`#--before-all-- section must contain a code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the code language is not javascript', () => {
|
||||
expect(() => plugin(withNonJSHookAST, file)).toThrow(
|
||||
`#--before-all-- hook must be written in JavaScript`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have an output to match the snapshot', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
40
tools/challenge-parser/parser/plugins/add-hooks.js
Normal file
40
tools/challenge-parser/parser/plugins/add-hooks.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { getSection } = require('./utils/get-section');
|
||||
|
||||
function plugin() {
|
||||
return transformer;
|
||||
|
||||
function transformer(tree, file) {
|
||||
const beforeAll = getHook(tree, '--before-all--');
|
||||
const beforeEach = getHook(tree, '--before-each--');
|
||||
const afterEach = getHook(tree, '--after-each--');
|
||||
|
||||
if (!beforeAll && !beforeEach && !afterEach) return;
|
||||
|
||||
file.data.hooks = file.data.hooks = {
|
||||
...(beforeAll && { beforeAll }),
|
||||
...(beforeEach && { beforeEach }),
|
||||
...(afterEach && { afterEach })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getHook(tree, sectionName) {
|
||||
const section = getSection(tree, sectionName);
|
||||
|
||||
if (section.length === 0) return;
|
||||
if (section.length > 1)
|
||||
throw Error(
|
||||
`# ${sectionName} section must only contain a single code block`
|
||||
);
|
||||
|
||||
const codeNode = section[0];
|
||||
|
||||
if (codeNode.type !== 'code')
|
||||
throw Error(`# ${sectionName} section must contain a code block`);
|
||||
if (codeNode.lang !== 'javascript' && codeNode.lang !== 'js')
|
||||
throw Error(`# ${sectionName} hook must be written in JavaScript`);
|
||||
|
||||
return codeNode.value;
|
||||
}
|
||||
|
||||
module.exports = plugin;
|
||||
149
tools/challenge-parser/parser/plugins/add-hooks.test.js
Normal file
149
tools/challenge-parser/parser/plugins/add-hooks.test.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const parseFixture = require('../__fixtures__/parse-fixture');
|
||||
|
||||
const addBeforeHook = require('./add-hooks');
|
||||
|
||||
describe('add-before-hook plugin', () => {
|
||||
let withBeforeHookAST,
|
||||
withInvalidHookAST,
|
||||
withAnotherInvalidHookAST,
|
||||
withNonJSHookAST,
|
||||
withBeforeEachHookAST,
|
||||
withInvalidBeforeEachHookAST,
|
||||
withAnotherInvalidBeforeEachHookAST,
|
||||
withNonJSBeforeEachHookAST,
|
||||
withAfterEachHookAST,
|
||||
withInvalidAfterEachHookAST,
|
||||
withAnotherInvalidAfterEachHookAST,
|
||||
withNonJSAfterEachHookAST;
|
||||
|
||||
const plugin = addBeforeHook();
|
||||
let file = { data: {} };
|
||||
|
||||
beforeAll(async () => {
|
||||
withBeforeHookAST = await parseFixture('with-before-hook.md');
|
||||
withInvalidHookAST = await parseFixture('with-invalid-before-hook.md');
|
||||
withAnotherInvalidHookAST = await parseFixture(
|
||||
'with-another-invalid-before-hook.md'
|
||||
);
|
||||
withNonJSHookAST = await parseFixture('with-non-js-before-hook.md');
|
||||
withBeforeEachHookAST = await parseFixture('with-before-each-hook.md');
|
||||
withInvalidBeforeEachHookAST = await parseFixture(
|
||||
'with-invalid-before-each-hook.md'
|
||||
);
|
||||
withAnotherInvalidBeforeEachHookAST = await parseFixture(
|
||||
'with-another-invalid-before-each-hook.md'
|
||||
);
|
||||
withNonJSBeforeEachHookAST = await parseFixture(
|
||||
'with-non-js-before-each-hook.md'
|
||||
);
|
||||
withAfterEachHookAST = await parseFixture('with-after-each-hook.md');
|
||||
withInvalidAfterEachHookAST = await parseFixture(
|
||||
'with-invalid-after-each-hook.md'
|
||||
);
|
||||
withAnotherInvalidAfterEachHookAST = await parseFixture(
|
||||
'with-another-invalid-after-each-hook.md'
|
||||
);
|
||||
withNonJSAfterEachHookAST = await parseFixture(
|
||||
'with-non-js-after-each-hook.md'
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
file = { data: {} };
|
||||
});
|
||||
|
||||
it('returns a function', () => {
|
||||
expect(typeof plugin).toEqual('function');
|
||||
});
|
||||
|
||||
it('adds a `hooks` property to `file.data`', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect('hooks' in file.data).toBe(true);
|
||||
});
|
||||
|
||||
it('populates `hooks.beforeAll` with the contents of the code block', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect(file.data.hooks.beforeAll).toBe(`// before all code
|
||||
function foo() {
|
||||
return 'bar';
|
||||
}
|
||||
foo();`);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeAll section has more than one child', () => {
|
||||
expect(() => plugin(withInvalidHookAST, file)).toThrow(
|
||||
`# --before-all-- section must only contain a single code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeAll section does not contain a code block', () => {
|
||||
expect(() => plugin(withAnotherInvalidHookAST, file)).toThrow(
|
||||
`# --before-all-- section must contain a code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the code language is not javascript', () => {
|
||||
expect(() => plugin(withNonJSHookAST, file)).toThrow(
|
||||
`# --before-all-- hook must be written in JavaScript`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have an output to match the snapshot', () => {
|
||||
plugin(withBeforeHookAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('populates `hooks.beforeEach` with the contents of the code block', () => {
|
||||
plugin(withBeforeEachHookAST, file);
|
||||
expect(file.data.hooks.beforeEach).toBe(`// before each code
|
||||
function setup() {
|
||||
return 'initialized';
|
||||
}
|
||||
setup();`);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeEach section has more than one child', () => {
|
||||
expect(() => plugin(withInvalidBeforeEachHookAST, file)).toThrow(
|
||||
`# --before-each-- section must only contain a single code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeEach section does not contain a code block', () => {
|
||||
expect(() => plugin(withAnotherInvalidBeforeEachHookAST, file)).toThrow(
|
||||
`# --before-each-- section must contain a code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the beforeEach code language is not javascript', () => {
|
||||
expect(() => plugin(withNonJSBeforeEachHookAST, file)).toThrow(
|
||||
`# --before-each-- hook must be written in JavaScript`
|
||||
);
|
||||
});
|
||||
|
||||
it('populates `hooks.afterEach` with the contents of the code block', () => {
|
||||
plugin(withAfterEachHookAST, file);
|
||||
expect(file.data.hooks.afterEach).toBe(`// after each code
|
||||
function cleanup() {
|
||||
return 'cleaned up';
|
||||
}
|
||||
cleanup();`);
|
||||
});
|
||||
|
||||
it('should throw an error if the afterEach section has more than one child', () => {
|
||||
expect(() => plugin(withInvalidAfterEachHookAST, file)).toThrow(
|
||||
`# --after-each-- section must only contain a single code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the afterEach section does not contain a code block', () => {
|
||||
expect(() => plugin(withAnotherInvalidAfterEachHookAST, file)).toThrow(
|
||||
`# --after-each-- section must contain a code block`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the afterEach code language is not javascript', () => {
|
||||
expect(() => plugin(withNonJSAfterEachHookAST, file)).toThrow(
|
||||
`# --after-each-- hook must be written in JavaScript`
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user