Skip to content

Commit 6624ef4

Browse files
fix(rehype-shiki): sequential codeboxes are merged together
1 parent 79dc769 commit 6624ef4

File tree

3 files changed

+464
-132
lines changed

3 files changed

+464
-132
lines changed

packages/rehype-shiki/src/__tests__/highlighter.test.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ mock.module('shiki/themes/nord.mjs', {
1919
defaultExport: { name: 'nord', colors: { 'editor.background': '#2e3440' } },
2020
});
2121

22-
describe('createHighlighter', async () => {
22+
describe('createHighlighter', { concurrency: true }, async () => {
2323
const { createHighlighter } = await import('../highlighter.mjs');
2424

2525
describe('getLanguageDisplayName', () => {

packages/rehype-shiki/src/__tests__/plugin.test.mjs

Lines changed: 338 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@ mock.module('unist-util-visit', {
1919
namedExports: { visit: mockVisit, SKIP: Symbol() },
2020
});
2121

22-
describe('rehypeShikiji', async () => {
22+
describe('rehypeShikiji', { concurrency: true }, async () => {
2323
const { default: rehypeShikiji } = await import('../plugin.mjs');
2424
const mockTree = { type: 'root', children: [] };
2525

26-
it('calls visit twice', () => {
26+
it('calls visit once', () => {
2727
mockVisit.mock.resetCalls();
28+
2829
rehypeShikiji()(mockTree);
29-
assert.strictEqual(mockVisit.mock.calls.length, 2);
30+
31+
assert.strictEqual(mockVisit.mock.calls.length, 1);
3032
});
3133

32-
it('creates CodeTabs for multiple code blocks', () => {
33-
const parent = {
34+
it('does not create CodeTabs for non-CJS/ESM code blocks', () => {
35+
const treeToTransform = {
36+
type: 'root',
3437
children: [
3538
{
3639
tagName: 'pre',
@@ -55,13 +58,336 @@ describe('rehypeShikiji', async () => {
5558
],
5659
};
5760

58-
mockVisit.mock.mockImplementation((tree, selector, visitor) => {
59-
if (selector === 'element') {
60-
visitor(parent.children[0], 0, parent);
61-
}
62-
});
61+
rehypeShikiji()(treeToTransform);
6362

64-
rehypeShikiji()(mockTree);
65-
assert.ok(parent.children.some(child => child.tagName === 'CodeTabs'));
63+
assert.strictEqual(
64+
treeToTransform.children.length,
65+
2,
66+
'Should not group non-CJS/ESM blocks'
67+
);
68+
assert.ok(
69+
treeToTransform.children.every(child => child.tagName === 'pre'),
70+
'All children should remain pre elements'
71+
);
72+
// Ensure no CodeTabs were created
73+
assert.ok(
74+
!treeToTransform.children.some(child => child.tagName === 'CodeTabs'),
75+
'CodeTabs should not be created for JS/TS'
76+
);
77+
});
78+
79+
it('If there are a sequence of codeblock of CJS/ESM, it should create pairs of CodeTabs', () => {
80+
const parent = {
81+
type: 'root',
82+
children: [
83+
{
84+
tagName: 'pre',
85+
children: [
86+
{
87+
tagName: 'code',
88+
data: { meta: 'displayName="CJS"' },
89+
properties: { className: ['language-cjs'] },
90+
},
91+
],
92+
},
93+
{
94+
tagName: 'pre',
95+
children: [
96+
{
97+
tagName: 'code',
98+
data: { meta: 'displayName="ESM"' },
99+
properties: { className: ['language-esm'] },
100+
},
101+
],
102+
},
103+
{
104+
tagName: 'pre',
105+
children: [
106+
{
107+
tagName: 'code',
108+
data: { meta: 'displayName="CJS"' },
109+
properties: { className: ['language-cjs'] },
110+
},
111+
],
112+
},
113+
{
114+
tagName: 'pre',
115+
children: [
116+
{
117+
tagName: 'code',
118+
data: { meta: 'displayName="ESM"' },
119+
properties: { className: ['language-esm'] },
120+
},
121+
],
122+
},
123+
],
124+
};
125+
126+
rehypeShikiji()(parent);
127+
128+
assert.strictEqual(
129+
parent.children.length,
130+
2,
131+
'Should create two CodeTabs groups'
132+
);
133+
134+
// Check first CodeTabs group
135+
const firstGroup = parent.children[0];
136+
assert.strictEqual(
137+
firstGroup.tagName,
138+
'CodeTabs',
139+
'Group 1 should be CodeTabs'
140+
);
141+
assert.strictEqual(
142+
firstGroup.properties.languages,
143+
'cjs|esm',
144+
'Group 1 languages should be cjs|esm'
145+
);
146+
assert.strictEqual(
147+
firstGroup.properties.displayNames,
148+
'CJS|ESM',
149+
'Group 1 displayNames should be CJS|ESM'
150+
);
151+
assert.ok(
152+
Array.isArray(firstGroup.children) && firstGroup.children.length === 2,
153+
'Group 1 should contain 2 code blocks'
154+
);
155+
assert.strictEqual(
156+
firstGroup.children[0].children[0].properties.className[0],
157+
'language-cjs',
158+
'Group 1, Block 1 should be CJS'
159+
);
160+
assert.strictEqual(
161+
firstGroup.children[1].children[0].properties.className[0],
162+
'language-esm',
163+
'Group 1, Block 2 should be ESM'
164+
);
165+
166+
// Check second CodeTabs group
167+
const secondGroup = parent.children[1];
168+
assert.strictEqual(
169+
secondGroup.tagName,
170+
'CodeTabs',
171+
'Group 2 should be CodeTabs'
172+
);
173+
assert.strictEqual(
174+
secondGroup.properties.languages,
175+
'cjs|esm',
176+
'Group 2 languages should be cjs|esm'
177+
);
178+
assert.strictEqual(
179+
secondGroup.properties.displayNames,
180+
'CJS|ESM',
181+
'Group 2 displayNames should be CJS|ESM'
182+
);
183+
assert.ok(
184+
Array.isArray(secondGroup.children) && secondGroup.children.length === 2,
185+
'Group 2 should contain 2 code blocks'
186+
);
187+
assert.strictEqual(
188+
secondGroup.children[0].children[0].properties.className[0],
189+
'language-cjs',
190+
'Group 2, Block 1 should be CJS'
191+
);
192+
assert.strictEqual(
193+
secondGroup.children[1].children[0].properties.className[0],
194+
'language-esm',
195+
'Group 2, Block 2 should be ESM'
196+
);
197+
});
198+
199+
it("if itsn't a squence of cjs/esm codeblock, it should not pair them", () => {
200+
const parent = {
201+
type: 'root',
202+
children: [
203+
{
204+
tagName: 'pre',
205+
children: [
206+
{
207+
tagName: 'code',
208+
data: { meta: 'displayName="CJS"' },
209+
properties: { className: ['language-cjs'] },
210+
},
211+
],
212+
},
213+
{
214+
tagName: 'pre',
215+
children: [
216+
{
217+
tagName: 'code',
218+
data: { meta: 'displayName="ESM"' },
219+
properties: { className: ['language-esm'] },
220+
},
221+
],
222+
},
223+
{
224+
tagName: 'pre',
225+
children: [
226+
{
227+
tagName: 'code',
228+
data: { meta: 'displayName="TS"' },
229+
properties: { className: ['language-ts'] },
230+
},
231+
],
232+
},
233+
],
234+
};
235+
236+
rehypeShikiji()(parent);
237+
238+
assert.strictEqual(
239+
parent.children.length,
240+
3,
241+
'Should not create CodeTabs groups'
242+
);
243+
244+
// Check first code block
245+
const firstBlock = parent.children[0];
246+
assert.strictEqual(
247+
firstBlock.tagName,
248+
'pre',
249+
'First block should be a pre'
250+
);
251+
assert.strictEqual(
252+
firstBlock.children[0].tagName,
253+
'code',
254+
'First block should contain a code element'
255+
);
256+
assert.strictEqual(
257+
firstBlock.children[0].properties.className[0],
258+
'language-cjs',
259+
'First block should be CJS'
260+
);
261+
262+
// Check second code block
263+
const secondBlock = parent.children[1];
264+
assert.strictEqual(
265+
secondBlock.tagName,
266+
'pre',
267+
'Second block should be a pre'
268+
);
269+
assert.strictEqual(
270+
secondBlock.children[0].tagName,
271+
'code',
272+
'Second block should contain a code element'
273+
);
274+
assert.strictEqual(
275+
secondBlock.children[0].properties.className[0],
276+
'language-esm',
277+
'Second block should be ESM'
278+
);
279+
280+
// Check third code block
281+
const thirdBlock = parent.children[2];
282+
assert.strictEqual(
283+
thirdBlock.tagName,
284+
'pre',
285+
'Third block should be a pre'
286+
);
287+
assert.strictEqual(
288+
thirdBlock.children[0].tagName,
289+
'code',
290+
'Third block should contain a code element'
291+
);
292+
assert.strictEqual(
293+
thirdBlock.children[0].properties.className[0],
294+
'language-ts',
295+
'Third block should be TS'
296+
);
297+
298+
const parentBis = {
299+
type: 'root',
300+
children: [
301+
{
302+
tagName: 'pre',
303+
children: [
304+
{
305+
tagName: 'code',
306+
data: { meta: 'displayName="package.json"' },
307+
properties: { className: ['language-json'] },
308+
},
309+
],
310+
},
311+
{
312+
tagName: 'pre',
313+
children: [
314+
{
315+
tagName: 'code',
316+
data: { meta: 'displayName="workflow"' },
317+
properties: { className: ['language-yaml'] },
318+
},
319+
],
320+
},
321+
{
322+
tagName: 'pre',
323+
children: [
324+
{
325+
tagName: 'code',
326+
data: { meta: 'displayName="mod.ts"' },
327+
properties: { className: ['language-ts'] },
328+
},
329+
],
330+
},
331+
],
332+
};
333+
334+
rehypeShikiji()(parentBis);
335+
336+
assert.strictEqual(
337+
parentBis.children.length,
338+
3,
339+
'Should not create CodeTabs groups for different languages'
340+
);
341+
// Check first code block
342+
const firstBlockBis = parentBis.children[0];
343+
assert.strictEqual(
344+
firstBlockBis.tagName,
345+
'pre',
346+
'First block should be a pre'
347+
);
348+
assert.strictEqual(
349+
firstBlockBis.children[0].tagName,
350+
'code',
351+
'First block should contain a code element'
352+
);
353+
assert.strictEqual(
354+
firstBlockBis.children[0].properties.className[0],
355+
'language-json',
356+
'First block should be JSON'
357+
);
358+
// Check second code block
359+
const secondBlockBis = parentBis.children[1];
360+
assert.strictEqual(
361+
secondBlockBis.tagName,
362+
'pre',
363+
'Second block should be a pre'
364+
);
365+
assert.strictEqual(
366+
secondBlockBis.children[0].tagName,
367+
'code',
368+
'Second block should contain a code element'
369+
);
370+
assert.strictEqual(
371+
secondBlockBis.children[0].properties.className[0],
372+
'language-yaml',
373+
'Second block should be YAML'
374+
);
375+
// Check third code block
376+
const thirdBlockBis = parentBis.children[2];
377+
assert.strictEqual(
378+
thirdBlockBis.tagName,
379+
'pre',
380+
'Third block should be a pre'
381+
);
382+
assert.strictEqual(
383+
thirdBlockBis.children[0].tagName,
384+
'code',
385+
'Third block should contain a code element'
386+
);
387+
assert.strictEqual(
388+
thirdBlockBis.children[0].properties.className[0],
389+
'language-ts',
390+
'Third block should be TS'
391+
);
66392
});
67393
});

0 commit comments

Comments
 (0)