Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lexical-markdown] Add test to keep code language #6259

Merged
merged 3 commits into from
Jun 11, 2024
Merged
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
46 changes: 37 additions & 9 deletions packages/lexical-code/src/CodeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ export type SerializedCodeNode = Spread<
SerializedElementNode
>;

const mapToPrismLanguage = (
const isLanguageSupportedByPrism = (
language: string | null | undefined,
): string | null | undefined => {
// eslint-disable-next-line no-prototype-builtins
return language != null && window.Prism.languages.hasOwnProperty(language)
? language
: undefined;
): boolean => {
try {
// eslint-disable-next-line no-prototype-builtins
return language ? window.Prism.languages.hasOwnProperty(language) : false;
} catch {
return false;
}
};

function hasChildDOMNodeTag(node: Node, tagName: string) {
Expand All @@ -67,12 +69,15 @@ function hasChildDOMNodeTag(node: Node, tagName: string) {
return false;
}

const LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';
const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
const HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';

/** @noInheritDoc */
export class CodeNode extends ElementNode {
/** @internal */
__language: string | null | undefined;
/** @internal */
__isSyntaxHighlightSupported: boolean;

static getType(): string {
return 'code';
Expand All @@ -84,7 +89,8 @@ export class CodeNode extends ElementNode {

constructor(language?: string | null | undefined, key?: NodeKey) {
super(key);
this.__language = mapToPrismLanguage(language);
this.__language = language;
this.__isSyntaxHighlightSupported = isLanguageSupportedByPrism(language);
}

// View
Expand All @@ -95,6 +101,10 @@ export class CodeNode extends ElementNode {
const language = this.getLanguage();
if (language) {
element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);

if (this.getIsSyntaxHighlightSupported()) {
element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
}
}
return element;
}
Expand All @@ -109,9 +119,17 @@ export class CodeNode extends ElementNode {
if (language) {
if (language !== prevLanguage) {
dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);

if (this.__isSyntaxHighlightSupported) {
dom.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
}
}
} else if (prevLanguage) {
dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE);

if (prevNode.__isSyntaxHighlightSupported) {
dom.removeAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE);
}
}
return false;
}
Expand All @@ -123,6 +141,10 @@ export class CodeNode extends ElementNode {
const language = this.getLanguage();
if (language) {
element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);

if (this.getIsSyntaxHighlightSupported()) {
element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
}
}
return {element};
}
Expand Down Expand Up @@ -304,12 +326,18 @@ export class CodeNode extends ElementNode {

setLanguage(language: string): void {
const writable = this.getWritable();
writable.__language = mapToPrismLanguage(language);
writable.__language = language;
writable.__isSyntaxHighlightSupported =
isLanguageSupportedByPrism(language);
}

getLanguage(): string | null | undefined {
return this.getLatest().__language;
}

getIsSyntaxHighlightSupported(): boolean {
return this.getLatest().__isSyntaxHighlightSupported;
}
}

export function $createCodeNode(
Expand Down
26 changes: 13 additions & 13 deletions packages/lexical-code/src/__tests__/unit/LexicalCodeNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span></code>',
);

// CodeNode should only render diffs, make sure that the TabNode is not cloned when
Expand All @@ -200,7 +200,7 @@ describe('LexicalCodeNode tests', () => {
}),
);
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">foo</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">foo</span></code>',
);
});

Expand All @@ -224,7 +224,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span data-lexical-text="true">\t</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span data-lexical-text="true">\t</span></code>',
);
});

Expand All @@ -248,7 +248,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
);

await editor.update(() => {
Expand All @@ -261,7 +261,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span></code>',
);
});

Expand All @@ -285,7 +285,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
);

await editor.update(() => {
Expand All @@ -299,7 +299,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span></code>',
);
});

Expand All @@ -326,13 +326,13 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
);

await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
);
});
Expand All @@ -351,7 +351,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
.toBe(`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">hello</span><br><span data-lexical-text="true">\t</span></code>`);
});

Expand All @@ -375,7 +375,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">hello</span></code>',
'<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">hello</span></code>',
);
});

Expand All @@ -395,7 +395,7 @@ describe('LexicalCodeNode tests', () => {
keyEvent.altKey = true;
await editor.dispatchCommand(KEY_ARROW_UP_COMMAND, keyEvent);
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
.toBe(`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span></code>`);
});

Expand Down Expand Up @@ -428,7 +428,7 @@ describe('LexicalCodeNode tests', () => {
keyEvent.altKey = true;
await editor.dispatchCommand(KEY_ARROW_DOWN_COMMAND, keyEvent);
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
.toBe(`<code spellcheck="false" data-language="javascript" data-highlight-language="javascript" dir="ltr" data-gutter="1
2
3"><span data-lexical-text="true">mno</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">pqr</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span><br><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span></code>`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ describe('Markdown', () => {
html: '<pre spellcheck="false"><span style="white-space: pre-wrap;">Code</span></pre>',
md: '```\nCode\n```',
},
{
html: '<pre spellcheck="false" data-language="javascript" data-highlight-language="javascript"><span style="white-space: pre-wrap;">Code</span></pre>',
md: '```javascript\nCode\n```',
},
{
// Should always preserve language in md but keep data-highlight-language only for supported languages
html: '<pre spellcheck="false" data-language="unknown"><span style="white-space: pre-wrap;">Code</span></pre>',
md: '```unknown\nCode\n```',
},
{
// Import only: prefix tabs will be removed for export
html: '<pre spellcheck="false"><span style="white-space: pre-wrap;">Code</span></pre>',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ test.describe('CodeActionMenu', () => {
dir="ltr"
spellcheck="false"
data-gutter="1"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true"></span>
<span class="PlaygroundEditorTheme__tokenAttr" data-lexical-text="true">
Expand Down Expand Up @@ -227,6 +228,7 @@ test.describe('CodeActionMenu', () => {
dir="ltr"
spellcheck="false"
data-gutter="12"
data-language="javascript"
data-highlight-language="javascript">
<span class="PlaygroundEditorTheme__tokenAttr" data-lexical-text="true">
const
Expand Down Expand Up @@ -271,6 +273,7 @@ test.describe('CodeActionMenu', () => {
dir="ltr"
spellcheck="false"
data-gutter="1"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true">cons luci</span>
<span class="PlaygroundEditorTheme__tokenOperator" data-lexical-text="true">
Expand Down
13 changes: 13 additions & 0 deletions packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="1"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenFunction"
Expand Down Expand Up @@ -128,6 +129,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="1"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenFunction"
Expand Down Expand Up @@ -225,6 +227,7 @@ test.describe('CodeBlock', () => {
dir="ltr"
spellcheck="false"
data-gutter="12345"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true">foo</span>
<br />
Expand Down Expand Up @@ -254,6 +257,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="1"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true">select</span>
<span
Expand All @@ -275,6 +279,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="1"
data-language="sql"
data-highlight-language="sql">
<span
class="PlaygroundEditorTheme__tokenAttr"
Expand Down Expand Up @@ -333,6 +338,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenFunction"
Expand Down Expand Up @@ -421,6 +427,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenAttr"
Expand Down Expand Up @@ -492,6 +499,7 @@ test.describe('CodeBlock', () => {
dir="ltr"
spellcheck="false"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
Expand Down Expand Up @@ -565,6 +573,7 @@ test.describe('CodeBlock', () => {
dir="ltr"
spellcheck="false"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true"></span>
<span
Expand Down Expand Up @@ -635,6 +644,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenAttr"
Expand Down Expand Up @@ -702,6 +712,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenFunction"
Expand Down Expand Up @@ -773,6 +784,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
dir="ltr"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenFunction"
Expand Down Expand Up @@ -899,6 +911,7 @@ test.describe('CodeBlock', () => {
dir="ltr"
spellcheck="false"
data-gutter="12"
data-language="javascript"
data-highlight-language="javascript">
<span data-lexical-text="true"></span>
<span data-lexical-text="true">a b</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test.describe('HTML CopyAndPaste', () => {
await focusEditor(page);

const clipboard = {
'text/html': `<meta charset='utf-8'><p class="x1f6kntn x1fcty0u x16h55sf x12nagc xdj266r" dir="ltr"><span>Code block</span></p><code class="x1f6kntn x1fcty0u x16h55sf x1xmf6yo x1e56ztr x1q8sqs3 xeq4nuv x1lliihq xz9dl7a xn6708d xsag5q8 x1ye3gou" spellcheck="false" data-highlight-language="javascript"><span class="xuc5kci">function</span><span> </span><span class="xu88d7e">foo</span><span class="x1noocy9">(</span><span class="x1noocy9">)</span><span> </span><span class="x1noocy9">{</span><br><span> </span><span class="xuc5kci">return</span><span> </span><span class="x180nigk">'Hey there'</span><span class="x1noocy9">;</span><br><span class="x1noocy9">}</span></code><p class="x1f6kntn x1fcty0u x16h55sf x12nagc xdj266r" dir="ltr"><span>--end--</span></p>`,
'text/html': `<meta charset='utf-8'><p class="x1f6kntn x1fcty0u x16h55sf x12nagc xdj266r" dir="ltr"><span>Code block</span></p><code class="x1f6kntn x1fcty0u x16h55sf x1xmf6yo x1e56ztr x1q8sqs3 xeq4nuv x1lliihq xz9dl7a xn6708d xsag5q8 x1ye3gou" spellcheck="false" data-language="javascript" data-highlight-language="javascript"><span class="xuc5kci">function</span><span> </span><span class="xu88d7e">foo</span><span class="x1noocy9">(</span><span class="x1noocy9">)</span><span> </span><span class="x1noocy9">{</span><br><span> </span><span class="xuc5kci">return</span><span> </span><span class="x180nigk">'Hey there'</span><span class="x1noocy9">;</span><br><span class="x1noocy9">}</span></code><p class="x1f6kntn x1fcty0u x16h55sf x12nagc xdj266r" dir="ltr"><span>--end--</span></p>`,
};

await pasteFromClipboard(page, clipboard);
Expand All @@ -74,6 +74,7 @@ test.describe('HTML CopyAndPaste', () => {
dir="ltr"
spellcheck="false"
data-gutter="123"
data-language="javascript"
data-highlight-language="javascript">
<span
class="PlaygroundEditorTheme__tokenAttr"
Expand Down
Loading
Loading