diff --git a/packages/@ember/helper/index.ts b/packages/@ember/helper/index.ts index 579aa49daf2..77fc4a1113d 100644 --- a/packages/@ember/helper/index.ts +++ b/packages/@ember/helper/index.ts @@ -10,6 +10,10 @@ import { concat as glimmerConcat, get as glimmerGet, fn as glimmerFn, + gt as glimmerGt, + gte as glimmerGte, + lt as glimmerLt, + lte as glimmerLte, } from '@glimmer/runtime'; import { element as glimmerElement, uniqueId as glimmerUniqueId } from '@ember/-internals/glimmer'; import { type Opaque } from '@ember/-internals/utility-types'; @@ -470,6 +474,102 @@ export interface GetHelper extends Opaque<'helper:get'> {} export const fn = glimmerFn as FnHelper; export interface FnHelper extends Opaque<'helper:fn'> {} +/** + * The `{{gt}}` helper returns `true` if the first argument is greater than + * the second argument. + * + * ```js + * import { gt } from '@ember/helper'; + * + * + * ``` + * + * In strict-mode (gjs/gts) templates, `gt` is available as a keyword and + * does not need to be imported. + * + * @method gt + * @param {number} left + * @param {number} right + * @return {boolean} + * @public + */ +export const gt = glimmerGt as unknown as GtHelper; +export interface GtHelper extends Opaque<'helper:gt'> {} + +/** + * The `{{gte}}` helper returns `true` if the first argument is greater than + * or equal to the second argument. + * + * ```js + * import { gte } from '@ember/helper'; + * + * + * ``` + * + * In strict-mode (gjs/gts) templates, `gte` is available as a keyword and + * does not need to be imported. + * + * @method gte + * @param {number} left + * @param {number} right + * @return {boolean} + * @public + */ +export const gte = glimmerGte as unknown as GteHelper; +export interface GteHelper extends Opaque<'helper:gte'> {} + +/** + * The `{{lt}}` helper returns `true` if the first argument is less than + * the second argument. + * + * ```js + * import { lt } from '@ember/helper'; + * + * + * ``` + * + * In strict-mode (gjs/gts) templates, `lt` is available as a keyword and + * does not need to be imported. + * + * @method lt + * @param {number} left + * @param {number} right + * @return {boolean} + * @public + */ +export const lt = glimmerLt as unknown as LtHelper; +export interface LtHelper extends Opaque<'helper:lt'> {} + +/** + * The `{{lte}}` helper returns `true` if the first argument is less than + * or equal to the second argument. + * + * ```js + * import { lte } from '@ember/helper'; + * + * + * ``` + * + * In strict-mode (gjs/gts) templates, `lte` is available as a keyword and + * does not need to be imported. + * + * @method lte + * @param {number} left + * @param {number} right + * @return {boolean} + * @public + */ +export const lte = glimmerLte as unknown as LteHelper; +export interface LteHelper extends Opaque<'helper:lte'> {} + /** * The `element` helper lets you dynamically set the tag name of an element. * diff --git a/packages/@ember/template-compiler/lib/compile-options.ts b/packages/@ember/template-compiler/lib/compile-options.ts index c9db7a1777b..64fdc7f2fb6 100644 --- a/packages/@ember/template-compiler/lib/compile-options.ts +++ b/packages/@ember/template-compiler/lib/compile-options.ts @@ -1,4 +1,4 @@ -import { fn } from '@ember/helper'; +import { fn, gt, gte, lt, lte } from '@ember/helper'; import { on } from '@ember/modifier'; import { assert } from '@ember/debug'; import { @@ -26,6 +26,10 @@ export const RUNTIME_KEYWORDS_NAME = '__ember_keywords__'; export const keywords: Record = { fn, + gt, + gte, + lt, + lte, on, }; diff --git a/packages/@ember/template-compiler/lib/plugins/auto-import-builtins.ts b/packages/@ember/template-compiler/lib/plugins/auto-import-builtins.ts index 64503a3b5d9..a4e92ff715a 100644 --- a/packages/@ember/template-compiler/lib/plugins/auto-import-builtins.ts +++ b/packages/@ember/template-compiler/lib/plugins/auto-import-builtins.ts @@ -30,11 +30,35 @@ export default function autoImportBuiltins(env: EmberASTPluginEnvironment): ASTP if (isFn(node, hasLocal)) { rewriteKeyword(env, node, 'fn', '@ember/helper'); } + if (isGt(node, hasLocal)) { + rewriteKeyword(env, node, 'gt', '@ember/helper'); + } + if (isGte(node, hasLocal)) { + rewriteKeyword(env, node, 'gte', '@ember/helper'); + } + if (isLt(node, hasLocal)) { + rewriteKeyword(env, node, 'lt', '@ember/helper'); + } + if (isLte(node, hasLocal)) { + rewriteKeyword(env, node, 'lte', '@ember/helper'); + } }, MustacheStatement(node: AST.MustacheStatement) { if (isFn(node, hasLocal)) { rewriteKeyword(env, node, 'fn', '@ember/helper'); } + if (isGt(node, hasLocal)) { + rewriteKeyword(env, node, 'gt', '@ember/helper'); + } + if (isGte(node, hasLocal)) { + rewriteKeyword(env, node, 'gte', '@ember/helper'); + } + if (isLt(node, hasLocal)) { + rewriteKeyword(env, node, 'lt', '@ember/helper'); + } + if (isLte(node, hasLocal)) { + rewriteKeyword(env, node, 'lte', '@ember/helper'); + } }, }, }; @@ -68,3 +92,31 @@ function isFn( ): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } { return isPath(node.path) && node.path.original === 'fn' && !hasLocal('fn'); } + +function isGt( + node: AST.MustacheStatement | AST.SubExpression, + hasLocal: (k: string) => boolean +): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } { + return isPath(node.path) && node.path.original === 'gt' && !hasLocal('gt'); +} + +function isGte( + node: AST.MustacheStatement | AST.SubExpression, + hasLocal: (k: string) => boolean +): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } { + return isPath(node.path) && node.path.original === 'gte' && !hasLocal('gte'); +} + +function isLt( + node: AST.MustacheStatement | AST.SubExpression, + hasLocal: (k: string) => boolean +): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } { + return isPath(node.path) && node.path.original === 'lt' && !hasLocal('lt'); +} + +function isLte( + node: AST.MustacheStatement | AST.SubExpression, + hasLocal: (k: string) => boolean +): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } { + return isPath(node.path) && node.path.original === 'lte' && !hasLocal('lte'); +} diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/gt-runtime-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/gt-runtime-test.ts new file mode 100644 index 00000000000..14ae5e655c0 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/gt-runtime-test.ts @@ -0,0 +1,60 @@ +import { + GlimmerishComponent, + jitSuite, + RenderTest, + test, +} from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler/runtime'; + +class KeywordGtRuntime extends RenderTest { + static suiteName = 'keyword helper: gt (runtime)'; + + @test + 'explicit scope'() { + const compiled = template('{{if (gt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ a: 3, b: 2 }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'implicit scope (eval)'() { + let a = 3; + let b = 2; + hide(a); + hide(b); + const compiled = template('{{if (gt a b) "yes" "no"}}', { + strictMode: true, + eval() { + return eval(arguments[0]); + }, + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'no eval and no scope'() { + class Foo extends GlimmerishComponent { + a = 3; + b = 2; + static { + template('{{if (gt this.a this.b) "yes" "no"}}', { + strictMode: true, + component: this, + }); + } + } + this.renderComponent(Foo); + this.assertHTML('yes'); + } +} + +jitSuite(KeywordGtRuntime); + +const hide = (variable: unknown) => { + new Function(`return (${JSON.stringify(variable)});`); +}; diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/gt-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/gt-test.ts new file mode 100644 index 00000000000..c55bfdd8b9b --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/gt-test.ts @@ -0,0 +1,60 @@ +import { DEBUG } from '@glimmer/env'; +import { jitSuite, RenderTest, test } from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler'; +import { gt } from '@ember/helper'; + +class KeywordGt extends RenderTest { + static suiteName = 'keyword helper: gt'; + + @test + 'returns true when first arg is greater'() { + let a = 3; + let b = 2; + const compiled = template('{{if (gt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns false when first arg is equal'() { + let a = 2; + let b = 2; + const compiled = template('{{if (gt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test + 'returns false when first arg is less'() { + let a = 1; + let b = 2; + const compiled = template('{{if (gt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test({ skip: !DEBUG }) + 'throws if not called with exactly two arguments'(assert: Assert) { + let a = 1; + const compiled = template('{{gt a}}', { + strictMode: true, + scope: () => ({ gt, a }), + }); + + assert.throws(() => { + this.renderComponent(compiled); + }, /`gt` expects exactly two arguments/); + } +} + +jitSuite(KeywordGt); diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/gte-runtime-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/gte-runtime-test.ts new file mode 100644 index 00000000000..3b440a23080 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/gte-runtime-test.ts @@ -0,0 +1,40 @@ +import { + GlimmerishComponent, + jitSuite, + RenderTest, + test, +} from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler/runtime'; + +class KeywordGteRuntime extends RenderTest { + static suiteName = 'keyword helper: gte (runtime)'; + + @test + 'explicit scope'() { + const compiled = template('{{if (gte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ a: 2, b: 2 }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'no eval and no scope'() { + class Foo extends GlimmerishComponent { + a = 2; + b = 2; + static { + template('{{if (gte this.a this.b) "yes" "no"}}', { + strictMode: true, + component: this, + }); + } + } + this.renderComponent(Foo); + this.assertHTML('yes'); + } +} + +jitSuite(KeywordGteRuntime); diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/gte-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/gte-test.ts new file mode 100644 index 00000000000..bcaca7d7035 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/gte-test.ts @@ -0,0 +1,60 @@ +import { DEBUG } from '@glimmer/env'; +import { jitSuite, RenderTest, test } from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler'; +import { gte } from '@ember/helper'; + +class KeywordGte extends RenderTest { + static suiteName = 'keyword helper: gte'; + + @test + 'returns true when first arg is greater'() { + let a = 3; + let b = 2; + const compiled = template('{{if (gte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns true when first arg is equal'() { + let a = 2; + let b = 2; + const compiled = template('{{if (gte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns false when first arg is less'() { + let a = 1; + let b = 2; + const compiled = template('{{if (gte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ gte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test({ skip: !DEBUG }) + 'throws if not called with exactly two arguments'(assert: Assert) { + let a = 1; + const compiled = template('{{gte a}}', { + strictMode: true, + scope: () => ({ gte, a }), + }); + + assert.throws(() => { + this.renderComponent(compiled); + }, /`gte` expects exactly two arguments/); + } +} + +jitSuite(KeywordGte); diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/lt-runtime-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/lt-runtime-test.ts new file mode 100644 index 00000000000..bf1a86230e1 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/lt-runtime-test.ts @@ -0,0 +1,60 @@ +import { + GlimmerishComponent, + jitSuite, + RenderTest, + test, +} from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler/runtime'; + +class KeywordLtRuntime extends RenderTest { + static suiteName = 'keyword helper: lt (runtime)'; + + @test + 'explicit scope'() { + const compiled = template('{{if (lt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ a: 1, b: 2 }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'implicit scope (eval)'() { + let a = 1; + let b = 2; + hide(a); + hide(b); + const compiled = template('{{if (lt a b) "yes" "no"}}', { + strictMode: true, + eval() { + return eval(arguments[0]); + }, + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'no eval and no scope'() { + class Foo extends GlimmerishComponent { + a = 1; + b = 2; + static { + template('{{if (lt this.a this.b) "yes" "no"}}', { + strictMode: true, + component: this, + }); + } + } + this.renderComponent(Foo); + this.assertHTML('yes'); + } +} + +jitSuite(KeywordLtRuntime); + +const hide = (variable: unknown) => { + new Function(`return (${JSON.stringify(variable)});`); +}; diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/lt-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/lt-test.ts new file mode 100644 index 00000000000..122ee8a6592 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/lt-test.ts @@ -0,0 +1,60 @@ +import { DEBUG } from '@glimmer/env'; +import { jitSuite, RenderTest, test } from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler'; +import { lt } from '@ember/helper'; + +class KeywordLt extends RenderTest { + static suiteName = 'keyword helper: lt'; + + @test + 'returns true when first arg is less'() { + let a = 1; + let b = 2; + const compiled = template('{{if (lt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns false when first arg is equal'() { + let a = 2; + let b = 2; + const compiled = template('{{if (lt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test + 'returns false when first arg is greater'() { + let a = 3; + let b = 2; + const compiled = template('{{if (lt a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lt, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test({ skip: !DEBUG }) + 'throws if not called with exactly two arguments'(assert: Assert) { + let a = 1; + const compiled = template('{{lt a}}', { + strictMode: true, + scope: () => ({ lt, a }), + }); + + assert.throws(() => { + this.renderComponent(compiled); + }, /`lt` expects exactly two arguments/); + } +} + +jitSuite(KeywordLt); diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/lte-runtime-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/lte-runtime-test.ts new file mode 100644 index 00000000000..a5007816398 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/lte-runtime-test.ts @@ -0,0 +1,40 @@ +import { + GlimmerishComponent, + jitSuite, + RenderTest, + test, +} from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler/runtime'; + +class KeywordLteRuntime extends RenderTest { + static suiteName = 'keyword helper: lte (runtime)'; + + @test + 'explicit scope'() { + const compiled = template('{{if (lte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ a: 2, b: 2 }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'no eval and no scope'() { + class Foo extends GlimmerishComponent { + a = 2; + b = 2; + static { + template('{{if (lte this.a this.b) "yes" "no"}}', { + strictMode: true, + component: this, + }); + } + } + this.renderComponent(Foo); + this.assertHTML('yes'); + } +} + +jitSuite(KeywordLteRuntime); diff --git a/packages/@glimmer-workspace/integration-tests/test/keywords/lte-test.ts b/packages/@glimmer-workspace/integration-tests/test/keywords/lte-test.ts new file mode 100644 index 00000000000..fd985049ff3 --- /dev/null +++ b/packages/@glimmer-workspace/integration-tests/test/keywords/lte-test.ts @@ -0,0 +1,60 @@ +import { DEBUG } from '@glimmer/env'; +import { jitSuite, RenderTest, test } from '@glimmer-workspace/integration-tests'; + +import { template } from '@ember/template-compiler'; +import { lte } from '@ember/helper'; + +class KeywordLte extends RenderTest { + static suiteName = 'keyword helper: lte'; + + @test + 'returns true when first arg is less'() { + let a = 1; + let b = 2; + const compiled = template('{{if (lte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns true when first arg is equal'() { + let a = 2; + let b = 2; + const compiled = template('{{if (lte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('yes'); + } + + @test + 'returns false when first arg is greater'() { + let a = 3; + let b = 2; + const compiled = template('{{if (lte a b) "yes" "no"}}', { + strictMode: true, + scope: () => ({ lte, a, b }), + }); + this.renderComponent(compiled); + this.assertHTML('no'); + } + + @test({ skip: !DEBUG }) + 'throws if not called with exactly two arguments'(assert: Assert) { + let a = 1; + const compiled = template('{{lte a}}', { + strictMode: true, + scope: () => ({ lte, a }), + }); + + assert.throws(() => { + this.renderComponent(compiled); + }, /`lte` expects exactly two arguments/); + } +} + +jitSuite(KeywordLte); diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index 9ec4eb2b603..559a0c96cd6 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -35,7 +35,11 @@ export { array } from './lib/helpers/array'; export { concat } from './lib/helpers/concat'; export { fn } from './lib/helpers/fn'; export { get } from './lib/helpers/get'; +export { gt } from './lib/helpers/gt'; +export { gte } from './lib/helpers/gte'; export { hash } from './lib/helpers/hash'; +export { lt } from './lib/helpers/lt'; +export { lte } from './lib/helpers/lte'; export { invokeHelper } from './lib/helpers/invoke'; export { on } from './lib/modifiers/on'; export { renderComponent, renderMain, renderSync } from './lib/render'; diff --git a/packages/@glimmer/runtime/lib/helpers/gt.ts b/packages/@glimmer/runtime/lib/helpers/gt.ts new file mode 100644 index 00000000000..38101c1b566 --- /dev/null +++ b/packages/@glimmer/runtime/lib/helpers/gt.ts @@ -0,0 +1,9 @@ +import { DEBUG } from '@glimmer/env'; + +export const gt = (...args: unknown[]) => { + if (DEBUG && args.length !== 2) { + throw new Error(`\`gt\` expects exactly two arguments, but received ${args.length}.`); + } + + return (args[0] as number) > (args[1] as number); +}; diff --git a/packages/@glimmer/runtime/lib/helpers/gte.ts b/packages/@glimmer/runtime/lib/helpers/gte.ts new file mode 100644 index 00000000000..2b69a96e28e --- /dev/null +++ b/packages/@glimmer/runtime/lib/helpers/gte.ts @@ -0,0 +1,9 @@ +import { DEBUG } from '@glimmer/env'; + +export const gte = (...args: unknown[]) => { + if (DEBUG && args.length !== 2) { + throw new Error(`\`gte\` expects exactly two arguments, but received ${args.length}.`); + } + + return (args[0] as number) >= (args[1] as number); +}; diff --git a/packages/@glimmer/runtime/lib/helpers/lt.ts b/packages/@glimmer/runtime/lib/helpers/lt.ts new file mode 100644 index 00000000000..949b3c96ad8 --- /dev/null +++ b/packages/@glimmer/runtime/lib/helpers/lt.ts @@ -0,0 +1,9 @@ +import { DEBUG } from '@glimmer/env'; + +export const lt = (...args: unknown[]) => { + if (DEBUG && args.length !== 2) { + throw new Error(`\`lt\` expects exactly two arguments, but received ${args.length}.`); + } + + return (args[0] as number) < (args[1] as number); +}; diff --git a/packages/@glimmer/runtime/lib/helpers/lte.ts b/packages/@glimmer/runtime/lib/helpers/lte.ts new file mode 100644 index 00000000000..d67a5441318 --- /dev/null +++ b/packages/@glimmer/runtime/lib/helpers/lte.ts @@ -0,0 +1,9 @@ +import { DEBUG } from '@glimmer/env'; + +export const lte = (...args: unknown[]) => { + if (DEBUG && args.length !== 2) { + throw new Error(`\`lte\` expects exactly two arguments, but received ${args.length}.`); + } + + return (args[0] as number) <= (args[1] as number); +}; diff --git a/smoke-tests/scenarios/basic-test.ts b/smoke-tests/scenarios/basic-test.ts index 1a4399e4151..83d7c0f7fdd 100644 --- a/smoke-tests/scenarios/basic-test.ts +++ b/smoke-tests/scenarios/basic-test.ts @@ -401,6 +401,38 @@ function basicTest(scenarios: Scenarios, appName: string) { }); }); `, + 'comparison-helpers-as-keyword-test.gjs': ` + import { module, test } from 'qunit'; + import { setupRenderingTest } from 'ember-qunit'; + import { render } from '@ember/test-helpers'; + + import Component from '@glimmer/component'; + + class LtDemo extends Component { + + } + + module('comparison helpers as keywords', function(hooks) { + setupRenderingTest(hooks); + + test('lt, lte, gt, gte work without imports', async function(assert) { + await render(LtDemo); + assert.dom('[data-test="lt"]').hasText('yes'); + assert.dom('[data-test="lte-equal"]').hasText('yes'); + assert.dom('[data-test="gt"]').hasText('yes'); + assert.dom('[data-test="gte-equal"]').hasText('yes'); + assert.dom('[data-test="lt-false"]').hasText('no'); + assert.dom('[data-test="gt-false"]').hasText('no'); + }); + }); + `, 'fn-as-keyword-but-its-shadowed-test.gjs': ` import QUnit, { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit';