DEV: Convert highlighted-code to glimmer/gjs (#28914)

the radical change in the implementation doesn't stem from the glimmer migration, but rather the fact that previously the component was single-use – changing any of its args didn't (and couldn't) be reflected because hljs was replacing the nodes so all the ember bookkeeping was gone.

Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Jarek Radosz 2024-09-17 13:49:35 +02:00 committed by GitHub
parent 7b8343d482
commit 62d00722e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 88 additions and 73 deletions

View File

@ -0,0 +1,25 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { modifier } from "ember-modifier";
import highlightSyntax from "discourse/lib/highlight-syntax";
export default class HighlightedCode extends Component {
@service session;
@service siteSettings;
highlight = modifier(async (element) => {
const code = document.createElement("code");
code.classList.add(`lang-${this.args.lang}`);
code.textContent = this.args.code;
const pre = document.createElement("pre");
pre.appendChild(code);
element.replaceChildren(pre);
await highlightSyntax(pre, this.siteSettings, this.session);
});
<template>
<div {{this.highlight}}></div>
</template>
}

View File

@ -1 +0,0 @@
<pre><code class="lang-{{this.lang}}">{{this.code}}</code></pre>

View File

@ -1,11 +0,0 @@
import Component from "@ember/component";
import { observes, on } from "@ember-decorators/object";
import highlightSyntax from "discourse/lib/highlight-syntax";
export default class HighlightedCode extends Component {
@on("didInsertElement")
@observes("code")
_refresh() {
highlightSyntax(this.element, this.siteSettings, this.session);
}
}

View File

@ -0,0 +1,63 @@
import { tracked } from "@glimmer/tracking";
import { render, settled } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import HighlightedCode from "admin/components/highlighted-code";
module("Integration | Component | highlighted-code", function (hooks) {
setupRenderingTest(hooks);
test("highlighting code", async function (assert) {
await render(<template>
<HighlightedCode @lang="ruby" @code="def test; end" />
</template>);
assert.dom("code.lang-ruby.hljs .hljs-keyword").hasText("def");
});
test("large code blocks are not highlighted", async function (assert) {
const longCodeBlock = "puts a\n".repeat(15000);
await render(<template>
<HighlightedCode @lang="ruby" @code={{longCodeBlock}} />
</template>);
assert.dom("pre code").hasText(longCodeBlock);
});
test("highlighting code with lang=auto", async function (assert) {
await render(<template>
<HighlightedCode @lang="auto" @code="def test; end" />
</template>);
assert.dom("code.hljs").hasNoClass("lang-auto", "lang-auto is removed");
assert.dom("code.hljs").hasClass(/language-/, "language is detected");
assert
.dom("code.hljs")
.hasNoAttribute(
"data-unknown-hljs-lang",
"language is found from language- class"
);
});
test("re-highlights the code when it changes", async function (assert) {
class State {
@tracked code = "def foo; end";
}
const testState = new State();
await render(<template>
<HighlightedCode @lang="ruby" @code={{testState.code}} />
{{testState.code}}
</template>);
assert.dom("code.lang-ruby.hljs .hljs-title").hasText("foo");
testState.code = "def bar; end";
await settled();
assert.dom("code.lang-ruby.hljs .hljs-title").hasText("bar");
});
});

View File

@ -1,61 +0,0 @@
import { render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { module, test } from "qunit";
import highlightSyntax from "discourse/lib/highlight-syntax";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { query } from "discourse/tests/helpers/qunit-helpers";
const LONG_CODE_BLOCK = "puts a\n".repeat(15000);
module("Integration | Component | highlighted-code", function (hooks) {
setupRenderingTest(hooks);
test("highlighting code", async function (assert) {
this.set("code", "def test; end");
await render(hbs`<HighlightedCode @lang="ruby" @code={{this.code}} />`);
assert.strictEqual(
query("code.lang-ruby.hljs .hljs-keyword").innerText.trim(),
"def"
);
});
test("large code blocks are not highlighted", async function (assert) {
this.set("code", LONG_CODE_BLOCK);
await render(hbs`<HighlightedCode @lang="ruby" @code={{this.code}} />`);
assert.strictEqual(query("code").innerText.trim(), LONG_CODE_BLOCK.trim());
});
test("highlighting code with lang=auto", async function (assert) {
this.set("code", "def test; end");
await render(hbs`<HighlightedCode @lang="auto" @code={{this.code}} />`);
const codeElement = query("code.hljs");
assert.notOk(
codeElement.classList.contains("lang-auto"),
"lang-auto is removed"
);
assert.ok(
Array.from(codeElement.classList).some((className) => {
return className.startsWith("language-");
}),
"language is detected"
);
await highlightSyntax(
codeElement.parentElement, // <pre>
this.siteSettings,
this.session
);
assert.notOk(
codeElement.dataset.unknownHljsLang,
"language is found from language- class"
);
});
});