feat: support beforeEach and afterEach (#60921)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Oliver Eyton-Williams
2025-07-07 12:46:09 +02:00
committed by GitHub
parent 38cd1727e4
commit 2a7b220a4f
21 changed files with 549 additions and 165 deletions
@@ -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);
}
```
+2 -2
View File
@@ -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();
});
});
@@ -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;
@@ -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`
);
});
});