Skip to content

Commit f479583

Browse files
committed
iss1757 - extractor tests
1 parent ba4a4ba commit f479583

16 files changed

Lines changed: 6554 additions & 17 deletions

corsscripts/ascii/extractors/lastblock.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Extractor: lastblock
2-
// Returns the raw content of the last code_inline, or the full trimmed content
2+
// Returns the raw content of the last code_inline, or the full content
33
// of the last asciimath_block, in document order.
44
// Falls back to the final non-empty line of raw when no blocks are available.
55
export default function lastblock(raw, blocks) {
@@ -21,7 +21,7 @@ export default function lastblock(raw, blocks) {
2121
for (let i = lines.length - 1; i >= 0; i--) {
2222
const trimmed = lines[i].trim();
2323
if (trimmed !== '') {
24-
return trimmed;
24+
return lines[i];
2525
}
2626
}
2727
return 'ERROR';

corsscripts/ascii/extractors/lastcalc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Extractor: lastcalc
2-
// Returns the raw content of the last calculation block.
2+
// Returns the trimmed content of the last calculation block.
33
export default function lastcalc(raw, blocks) {
44
if (blocks) {
55
for (let i = blocks.length - 1; i >= 0; i--) {
66
if (blocks[i].type === 'calculation') {
7-
return blocks[i].raw;
7+
return blocks[i].raw.trim();
88
}
99
}
1010
}

corsscripts/ascii/extractors/lastexpr.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Extractor: lastexpr
2-
// Returns the raw content of the last code_inline, or the last non-empty line
2+
// Returns the trimmed content of the last code_inline, or the last non-empty line
33
// of the last asciimath_block, in document order.
44
// Falls back to the final non-empty line of raw when no blocks are available.
55
export default function lastexpr(raw, blocks) {
66
if (blocks && blocks.length > 0) {
77
for (let i = blocks.length - 1; i >= 0; i--) {
88
const block = blocks[i];
99
if (block.type === 'code_inline') {
10-
return block.raw;
10+
return block.raw.trim();
1111
}
1212
if (block.type === 'asciimath_block') {
1313
const lines = block.raw.split(/\r?\n/);

corsscripts/ascii/extractors/regexall.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Extractor: regexall
22
// [[extractor targetinput="ans2" type="regexall" regex="^f\\(x\\)\\s*=\\s*" /]]
3-
// Searches the entire raw input for all lines matching entry.regex and returns
3+
// Searches the entire raw input for all lines matching operation.regex and returns
44
// a JSON object of the form {"matches":[...]} set as answerEl.value.
5-
export default function regexall(raw, blocks, entry) {
6-
if (!entry || !entry.regex) {
7-
return;
5+
export default function regexall(raw, blocks, operation) {
6+
if (!operation || !operation.regex) {
7+
return 'ERROR';
88
}
9-
const pattern = new RegExp(entry.regex);
9+
const pattern = new RegExp(operation.regex);
1010
const matches = [];
1111

1212
for (const line of raw.split('\n')) {

corsscripts/ascii/extractors/regexmatch.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
// Note the scaped backslashes. Searches for a trimmed line beginning 'f(x) = ' where
44
// there can be any amount of whitespace around the equals. Returns 'f(x) = expr'.
55
// Scans blocks (bottom-up) for the last code_inline or asciimath_block line
6-
// matching entry.regex, then sets answerEl.value to the matched string.
7-
export default function regexmatch(raw, blocks, entry) {
8-
if (!entry || !entry.regex) {
9-
return;
6+
// matching operation.regex, then sets answerEl.value to the matched string.
7+
export default function regexmatch(raw, blocks, operation) {
8+
if (!operation || !operation.regex) {
9+
return 'ERROR';
1010
}
11-
const pattern = new RegExp(entry.regex);
11+
const pattern = new RegExp(operation.regex);
1212

1313
if (blocks && blocks.length > 0) {
1414
for (let i = blocks.length - 1; i >= 0; i--) {

corsscripts/ascii/filters/calculation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Filter: calculation
1+
// Filter: calculation - Currently for testing purposes.
22
// Finds text enclosed in @ characters on a single line and wraps it in double stars.
33
// e.g. "The answer is @x^2 + 1@ here" → "The answer is **x^2 + 1** here"
44

tests/jest/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import finalfunction from '../../corsscripts/ascii/extractors/finalfunction.js';
2+
3+
describe('finalfunction extractor', () => {
4+
5+
// ── Block-mode tests ──────────────────────────────────────────────────────
6+
7+
describe('with blocks', () => {
8+
test('returns expression from a matching code_inline block', () => {
9+
const blocks = [{ type: 'code_inline', raw: 'f(x) = x^2' }];
10+
expect(finalfunction('', blocks)).toBe('x^2');
11+
});
12+
13+
test('strips leading/trailing whitespace from code_inline before matching', () => {
14+
const blocks = [{ type: 'code_inline', raw: ' f(x) = sin(x) ' }];
15+
expect(finalfunction('', blocks)).toBe('sin(x)');
16+
});
17+
18+
test('returns last matching line from an asciimath_block', () => {
19+
const blocks = [{
20+
type: 'asciimath_block',
21+
raw: 'y = 3\nf(x) = x + 1'
22+
}];
23+
expect(finalfunction('', blocks)).toBe('x + 1');
24+
});
25+
26+
test('scans asciimath_block lines bottom-up and returns last match', () => {
27+
const blocks = [{
28+
type: 'asciimath_block',
29+
raw: 'f(x) = x\nf(x) = x^2\nsome other line'
30+
}];
31+
expect(finalfunction('', blocks)).toBe('x^2');
32+
});
33+
34+
test('scans blocks bottom-up, returning last block with a match', () => {
35+
const blocks = [
36+
{ type: 'code_inline', raw: 'f(x) = first' },
37+
{ type: 'code_inline', raw: 'f(x) = last' }
38+
];
39+
expect(finalfunction('', blocks)).toBe('last');
40+
});
41+
42+
test('skips non-matching code_inline blocks and finds earlier match', () => {
43+
const blocks = [
44+
{ type: 'code_inline', raw: 'f(x) = found' },
45+
{ type: 'code_inline', raw: 'y = x' }
46+
];
47+
expect(finalfunction('', blocks)).toBe('found');
48+
});
49+
50+
test('returns ERROR when no block line matches f(x) = pattern', () => {
51+
const blocks = [
52+
{ type: 'code_inline', raw: 'y = x' },
53+
{ type: 'asciimath_block', raw: 'a = 1\nb = 2' }
54+
];
55+
expect(finalfunction('', blocks)).toBe('ERROR');
56+
});
57+
58+
test('ignores blocks that are not code_inline or asciimath_block', () => {
59+
const blocks = [
60+
{ type: 'paragraph', raw: 'f(x) = ignored' },
61+
{ type: 'code_inline', raw: 'f(x) = found' },
62+
{ type: 'paragraph', raw: 'f(x) = ignored' },
63+
];
64+
expect(finalfunction('', blocks)).toBe('found');
65+
});
66+
67+
test('returns ERROR for an empty blocks array (falls back to raw, no match)', () => {
68+
expect(finalfunction('no match here', [])).toBe('ERROR');
69+
});
70+
71+
test('handles windows-style line endings in asciimath_block', () => {
72+
const blocks = [{
73+
type: 'asciimath_block',
74+
raw: 'y = 1\r\nf(x) = x^3'
75+
}];
76+
expect(finalfunction('', blocks)).toBe('x^3');
77+
});
78+
});
79+
80+
// ── Raw-fallback tests ────────────────────────────────────────────────────
81+
82+
describe('without blocks (raw fallback)', () => {
83+
test('returns expression from a plain raw line', () => {
84+
expect(finalfunction('f(x) = x + 2', null)).toBe('x + 2');
85+
});
86+
87+
test('strips backtick delimiters from raw input', () => {
88+
expect(finalfunction('`f(x) = x^2`', null)).toBe('x^2');
89+
});
90+
91+
test('scans raw lines bottom-up and returns last match', () => {
92+
const raw = 'f(x) = first\nsome text\nf(x) = last';
93+
expect(finalfunction(raw, null)).toBe('last');
94+
});
95+
96+
test('ignores non-matching lines in raw', () => {
97+
const raw = 'a = 1\nb = 2\nf(x) = expr\nb = 2';
98+
expect(finalfunction(raw, null)).toBe('expr');
99+
});
100+
101+
test('returns ERROR when no raw line matches', () => {
102+
expect(finalfunction('a = 1\nb = 2', null)).toBe('ERROR');
103+
});
104+
105+
test('returns ERROR for empty raw string with null blocks', () => {
106+
expect(finalfunction('', null)).toBe('ERROR');
107+
});
108+
});
109+
});
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import lastblock from '../../corsscripts/ascii/extractors/lastblock.js';
2+
3+
describe('lastblock extractor', () => {
4+
5+
// ── Block-mode tests ──────────────────────────────────────────────────────
6+
7+
describe('with blocks', () => {
8+
test('returns raw of a single code_inline block', () => {
9+
const blocks = [{ type: 'code_inline', raw: 'x^2' }];
10+
expect(lastblock('', blocks)).toBe('x^2');
11+
});
12+
13+
test('returns raw of a single asciimath_block', () => {
14+
const blocks = [{ type: 'asciimath_block', raw: 'x + 1\ny = 2' }];
15+
expect(lastblock('', blocks)).toBe('x + 1\ny = 2');
16+
});
17+
18+
test('returns raw of the last code_inline when multiple blocks exist', () => {
19+
const blocks = [
20+
{ type: 'code_inline', raw: 'first' },
21+
{ type: 'code_inline', raw: 'last' }
22+
];
23+
expect(lastblock('', blocks)).toBe('last');
24+
});
25+
26+
test('returns raw of the last asciimath_block when it is the last relevant block', () => {
27+
const blocks = [
28+
{ type: 'code_inline', raw: 'first' },
29+
{ type: 'asciimath_block', raw: 'second block' }
30+
];
31+
expect(lastblock('', blocks)).toBe('second block');
32+
});
33+
34+
test('scans bottom-up: last code_inline after an asciimath_block wins', () => {
35+
const blocks = [
36+
{ type: 'asciimath_block', raw: 'math block' },
37+
{ type: 'code_inline', raw: 'inline after' }
38+
];
39+
expect(lastblock('', blocks)).toBe('inline after');
40+
});
41+
42+
test('ignores blocks that are not code_inline or asciimath_block', () => {
43+
const blocks = [
44+
{ type: 'paragraph', raw: 'ignored' },
45+
{ type: 'heading', raw: 'also ignored' },
46+
];
47+
expect(lastblock('', blocks)).toBe('ERROR');
48+
});
49+
50+
test('mixes eligible and non-eligible blocks, returns last eligible', () => {
51+
const blocks = [
52+
{ type: 'code_inline', raw: 'inline' },
53+
{ type: 'paragraph', raw: 'para' }
54+
];
55+
expect(lastblock('', blocks)).toBe('inline');
56+
});
57+
});
58+
59+
// ── Raw-fallback tests ────────────────────────────────────────────────────
60+
61+
describe('without blocks (raw fallback)', () => {
62+
test('returns last non-empty line of raw', () => {
63+
expect(lastblock('line one\nline two', null)).toBe('line two');
64+
});
65+
66+
test('skips trailing empty lines in raw', () => {
67+
expect(lastblock('line one\nline two\n\n', null)).toBe('line two');
68+
});
69+
70+
test('returns the only non-empty line', () => {
71+
expect(lastblock('\n\n hello world \n', null)).toBe(' hello world ');
72+
});
73+
74+
test('returns line as-is (untrimmed) from raw', () => {
75+
expect(lastblock(' trimmed ', null)).toBe(' trimmed ');
76+
});
77+
78+
test('returns ERROR when raw is all empty lines', () => {
79+
expect(lastblock('\n\n\n', null)).toBe('ERROR');
80+
});
81+
82+
test('returns ERROR for empty raw string', () => {
83+
expect(lastblock('', null)).toBe('ERROR');
84+
});
85+
86+
test('handles windows-style line endings in raw', () => {
87+
expect(lastblock('first\r\nsecond', null)).toBe('second');
88+
});
89+
90+
test('falls back to raw when blocks is an empty array', () => {
91+
expect(lastblock('fallback line', [])).toBe('fallback line');
92+
});
93+
});
94+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import lastcalc from '../../corsscripts/ascii/extractors/lastcalc.js';
2+
3+
describe('lastcalc extractor', () => {
4+
5+
describe('with blocks', () => {
6+
test('returns trimmed content of a single calculation block', () => {
7+
const blocks = [{ type: 'calculation', raw: '1 + 1' }];
8+
expect(lastcalc('', blocks)).toBe('1 + 1');
9+
});
10+
11+
test('trims whitespace from the calculation block raw', () => {
12+
const blocks = [{ type: 'calculation', raw: ' x^2 ' }];
13+
expect(lastcalc('', blocks)).toBe('x^2');
14+
});
15+
16+
test('returns trimmed content of the last calculation block when multiple exist', () => {
17+
const blocks = [
18+
{ type: 'calculation', raw: 'first calc' },
19+
{ type: 'calculation', raw: 'last calc' }
20+
];
21+
expect(lastcalc('', blocks)).toBe('last calc');
22+
});
23+
24+
test('scans bottom-up: last calculation block wins over earlier ones', () => {
25+
const blocks = [
26+
{ type: 'code_inline', raw: 'irrelevant' },
27+
{ type: 'calculation', raw: 'calc one' },
28+
{ type: 'code_inline', raw: 'also irrelevant' },
29+
{ type: 'calculation', raw: 'calc two' }
30+
];
31+
expect(lastcalc('', blocks)).toBe('calc two');
32+
});
33+
34+
test('ignores non-calculation blocks', () => {
35+
const blocks = [
36+
{ type: 'code_inline', raw: 'not a calc' },
37+
{ type: 'calculation', raw: 'calc one' },
38+
{ type: 'asciimath_block', raw: 'also not a calc' }
39+
];
40+
expect(lastcalc('', blocks)).toBe('calc one');
41+
});
42+
43+
test('returns ERROR when blocks array contains no calculation blocks', () => {
44+
const blocks = [{ type: 'paragraph', raw: 'some text' }];
45+
expect(lastcalc('', blocks)).toBe('ERROR');
46+
});
47+
48+
test('returns ERROR for an empty blocks array', () => {
49+
expect(lastcalc('', [])).toBe('ERROR');
50+
});
51+
});
52+
53+
describe('without blocks', () => {
54+
test('returns ERROR when blocks is null', () => {
55+
expect(lastcalc('anything', null)).toBe('ERROR');
56+
});
57+
58+
test('returns ERROR when blocks is undefined', () => {
59+
expect(lastcalc('anything', undefined)).toBe('ERROR');
60+
});
61+
});
62+
});

0 commit comments

Comments
 (0)