From 5d9dca2ab5118e208d7afba6373273a4e16b14fa Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Wed, 18 Mar 2026 18:28:16 -0400
Subject: [PATCH] Extract rule: template-table-groups
---
README.md | 1 +
docs/rules/template-table-groups.md | 73 ++
lib/rules/template-table-groups.js | 231 ++++++
tests/lib/rules/template-table-groups.js | 893 +++++++++++++++++++++++
4 files changed, 1198 insertions(+)
create mode 100644 docs/rules/template-table-groups.md
create mode 100644 lib/rules/template-table-groups.js
create mode 100644 tests/lib/rules/template-table-groups.js
diff --git a/README.md b/README.md
index 4afd8f124e..fc82162cce 100644
--- a/README.md
+++ b/README.md
@@ -197,6 +197,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-no-nested-interactive](docs/rules/template-no-nested-interactive.md) | disallow nested interactive elements | | | |
| [template-no-nested-landmark](docs/rules/template-no-nested-landmark.md) | disallow nested landmark elements | | | |
| [template-no-pointer-down-event-binding](docs/rules/template-no-pointer-down-event-binding.md) | disallow pointer down event bindings | | | |
+| [template-table-groups](docs/rules/template-table-groups.md) | require table elements to use table grouping elements | | | |
### Best Practices
diff --git a/docs/rules/template-table-groups.md b/docs/rules/template-table-groups.md
new file mode 100644
index 0000000000..da5f9053b7
--- /dev/null
+++ b/docs/rules/template-table-groups.md
@@ -0,0 +1,73 @@
+# ember/template-table-groups
+
+
+
+Requires table elements to use table grouping elements.
+
+Tables should use ``, `
`, and `` elements to group related content. This improves accessibility for screen reader users and makes the table structure more semantic.
+
+## Rule Details
+
+This rule requires that `` elements use grouping elements (``, ``, ``) instead of having `` elements as direct children.
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
+
+ | Header |
+
+
+ | Data |
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## Options
+
+| Name | Type | Default | Description |
+| ----------------------------- | ---------- | ------- | ---------------------------------------------- |
+| `allowed-table-components` | `string[]` | `[]` | Component names treated as `` elements. |
+| `allowed-caption-components` | `string[]` | `[]` | Component names treated as ``. |
+| `allowed-colgroup-components` | `string[]` | `[]` | Component names treated as ``. |
+| `allowed-thead-components` | `string[]` | `[]` | Component names treated as ``. |
+| `allowed-tbody-components` | `string[]` | `[]` | Component names treated as ``. |
+| `allowed-tfoot-components` | `string[]` | `[]` | Component names treated as ``. |
+
+## References
+
+- [eslint-plugin-ember template-table-groups](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-table-groups.md)
+- [MDN - Table structure](https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced)
diff --git a/lib/rules/template-table-groups.js b/lib/rules/template-table-groups.js
new file mode 100644
index 0000000000..4d24cfead8
--- /dev/null
+++ b/lib/rules/template-table-groups.js
@@ -0,0 +1,231 @@
+const ALLOWED_TABLE_CHILDREN = ['caption', 'colgroup', 'thead', 'tbody', 'tfoot'];
+const CONTROL_FLOW_START_MARK = 0;
+const CONTROL_FLOW_END_MARK = 1;
+
+function dasherize(str) {
+ return str
+ .replaceAll('::', '/')
+ .replaceAll(/([\da-z])([A-Z])/g, '$1-$2')
+ .toLowerCase();
+}
+
+function isControlFlowHelper(node) {
+ if (node.type !== 'GlimmerBlockStatement' && node.type !== 'GlimmerMustacheStatement') {
+ return false;
+ }
+ const name = node.path?.original;
+ return ['if', 'unless', 'each', 'each-in', 'let', 'with'].includes(name);
+}
+
+function isIfOrUnless(node) {
+ const name = node.path?.original;
+ return name === 'if' || name === 'unless';
+}
+
+function getEffectiveChildren(children) {
+ return (children || []).flatMap((child) => {
+ if (isControlFlowHelper(child)) {
+ if (isIfOrUnless(child) && child.program && child.inverse) {
+ return [
+ CONTROL_FLOW_START_MARK,
+ ...getEffectiveChildren(child.program?.body || child.children || []),
+ CONTROL_FLOW_END_MARK,
+ CONTROL_FLOW_START_MARK,
+ ...getEffectiveChildren(child.inverse?.body || []),
+ CONTROL_FLOW_END_MARK,
+ ];
+ }
+ const body = child.program?.body || child.children || child.body?.body || [];
+ return getEffectiveChildren(body);
+ }
+ return [child];
+ });
+}
+
+function isAllowedTableChild(child, internalTags) {
+ switch (child.type) {
+ case 'GlimmerElementNode': {
+ const idx = ALLOWED_TABLE_CHILDREN.indexOf(child.tag);
+ if (idx > -1) {
+ return { allowed: true, indices: [idx] };
+ }
+ // Check @tagName attribute
+ const tagNameAttr = child.attributes?.find((a) => a.name === '@tagName');
+ if (tagNameAttr) {
+ const val = tagNameAttr.value?.type === 'GlimmerTextNode' ? tagNameAttr.value.chars : null;
+ const tIdx = ALLOWED_TABLE_CHILDREN.indexOf(val);
+ return { allowed: tIdx > -1, indices: tIdx > -1 ? [tIdx] : [] };
+ }
+ // Check custom component mapping
+ const dasherized = dasherize(child.tag);
+ const possibleIndices = internalTags.get(dasherized) || [];
+ if (possibleIndices.length > 0) {
+ return { allowed: true, indices: possibleIndices };
+ }
+ return { allowed: false };
+ }
+ case 'GlimmerBlockStatement':
+ case 'GlimmerMustacheStatement': {
+ // Check tagName hash pair
+ const tagNamePair = child.hash?.pairs?.find((p) => p.key === 'tagName');
+ if (tagNamePair) {
+ const val = tagNamePair.value?.value || tagNamePair.value?.chars;
+ const idx = ALLOWED_TABLE_CHILDREN.indexOf(val);
+ return { allowed: idx > -1, indices: idx > -1 ? [idx] : [] };
+ }
+ if (child.path?.original === 'yield') {
+ return { allowed: true, indices: [] };
+ }
+ const possibleIndices = internalTags.get(child.path?.original) || [];
+ if (possibleIndices.length > 0) {
+ return { allowed: true, indices: possibleIndices };
+ }
+ return { allowed: false };
+ }
+ case 'GlimmerCommentStatement':
+ case 'GlimmerMustacheCommentStatement': {
+ return { allowed: true, indices: [] };
+ }
+ case 'GlimmerTextNode': {
+ return { allowed: !/\S/.test(child.chars || ''), indices: [] };
+ }
+ default: {
+ return { allowed: false };
+ }
+ }
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'require table elements to use table grouping elements',
+ category: 'Accessibility',
+ url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-table-groups.md',
+ templateMode: 'both',
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ 'allowed-table-components': { type: 'array', items: { type: 'string' } },
+ 'allowed-caption-components': { type: 'array', items: { type: 'string' } },
+ 'allowed-colgroup-components': { type: 'array', items: { type: 'string' } },
+ 'allowed-thead-components': { type: 'array', items: { type: 'string' } },
+ 'allowed-tbody-components': { type: 'array', items: { type: 'string' } },
+ 'allowed-tfoot-components': { type: 'array', items: { type: 'string' } },
+ },
+ additionalProperties: false,
+ },
+ ],
+ messages: {
+ missing: 'Tables must have a table group (thead, tbody or tfoot).',
+ ordering:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ originallyFrom: {
+ name: 'ember-template-lint',
+ rule: 'lib/rules/table-groups.js',
+ docs: 'docs/rule/table-groups.md',
+ tests: 'test/unit/rules/table-groups-test.js',
+ },
+ },
+
+ create(context) {
+ const options = context.options[0] || {};
+ const outerTags = new Set(options['allowed-table-components'] || []);
+ const internalTags = new Map();
+
+ const componentKeys = [
+ 'allowed-caption-components',
+ 'allowed-colgroup-components',
+ 'allowed-thead-components',
+ 'allowed-tbody-components',
+ 'allowed-tfoot-components',
+ ];
+
+ for (const [index, key] of componentKeys.entries()) {
+ if (options[key]) {
+ for (const comp of options[key]) {
+ if (!internalTags.has(comp)) {
+ internalTags.set(comp, []);
+ }
+ internalTags.get(comp).push(index);
+ }
+ }
+ }
+
+ function isTableElement(node) {
+ if (node.tag === 'table') {
+ return true;
+ }
+ if (outerTags.has(dasherize(node.tag))) {
+ return true;
+ }
+ const tagNameAttr = node.attributes?.find((a) => a.name === '@tagName');
+ if (tagNameAttr) {
+ const val = tagNameAttr.value?.type === 'GlimmerTextNode' ? tagNameAttr.value.chars : null;
+ return val === 'table';
+ }
+ return false;
+ }
+
+ return {
+ GlimmerElementNode(node) {
+ if (!isTableElement(node)) {
+ return;
+ }
+
+ // Truly empty table (no content at all between tags) must have table groups
+ if (!node.children || node.children.length === 0) {
+ const sourceCode = context.getSourceCode();
+ const text = sourceCode.getText(node);
+ const openEnd = text.indexOf('>') + 1;
+ const closeStart = text.lastIndexOf('');
+ if (closeStart >= 0 && closeStart <= openEnd) {
+ context.report({ node, messageId: 'missing' });
+ return;
+ }
+ }
+
+ const children = getEffectiveChildren(node.children);
+
+ let currentAllowedMinimumIndices = new Set([0]);
+ const scopedIndices = [];
+
+ for (const child of children) {
+ if (child === CONTROL_FLOW_START_MARK) {
+ scopedIndices.push(currentAllowedMinimumIndices);
+ currentAllowedMinimumIndices = new Set(currentAllowedMinimumIndices);
+ continue;
+ }
+ if (child === CONTROL_FLOW_END_MARK) {
+ currentAllowedMinimumIndices = scopedIndices.pop();
+ continue;
+ }
+
+ const { allowed, indices } = isAllowedTableChild(child, internalTags);
+ if (!allowed) {
+ context.report({ node, messageId: 'missing' });
+ return;
+ }
+
+ if (indices.length > 0) {
+ const newAllowedMinimumIndices = new Set(
+ [...currentAllowedMinimumIndices].flatMap((currentIndex) =>
+ indices.filter((newIndex) => newIndex >= currentIndex)
+ )
+ );
+ if (newAllowedMinimumIndices.size === 0) {
+ context.report({ node, messageId: 'ordering' });
+ return;
+ }
+ currentAllowedMinimumIndices = newAllowedMinimumIndices;
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tests/lib/rules/template-table-groups.js b/tests/lib/rules/template-table-groups.js
new file mode 100644
index 0000000000..4e3799b4e8
--- /dev/null
+++ b/tests/lib/rules/template-table-groups.js
@@ -0,0 +1,893 @@
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/template-table-groups');
+const RuleTester = require('eslint').RuleTester;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+});
+
+ruleTester.run('template-table-groups', rule, {
+ valid: [
+ `
+
+
+ | Header |
+
+
+ | Data |
+
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ `
+ Not a table
+ `,
+
+ `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+
+ {{#if foo}}
+
+
+
+ {{else}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#if foo}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#unless foo}}
+
+
+
+ {{/unless}}
+
+ `,
+ `
+
+ {{#each foo as |bar|}}
+
+ bar
+
+ {{/each}}
+
+ `,
+ `
+
+ {{#each-in foo as |bar|}}
+
+ bar
+
+ {{/each-in}}
+
+ `,
+ `
+
+ {{#let foo as |bar|}}
+
+ bar
+
+ {{/let}}
+
+ `,
+ `
+
+ {{#with foo as |bar|}}
+
+ bar
+
+ {{/with}}
+
+ `,
+ `
+
+ {{#each foo as |bar|}}
+ {{#if bar}}
+ {{#unless baz}}
+
+ bar
+
+ {{/unless}}
+ {{/if}}
+ {{/each}}
+
+ `,
+ '{{some-component tagName="tbody"}}
',
+ '{{some-component tagName="thead"}}
',
+ '{{some-component tagName="tfoot"}}
',
+ '{{#some-component tagName="tbody"}}{{/some-component}}
',
+ '{{#some-component tagName="thead"}}{{/some-component}}
',
+ '{{#some-component tagName="tfoot"}}{{/some-component}}
',
+ '{{component "some-component" tagName="tbody"}}
',
+ '{{component "some-component" tagName="thead"}}
',
+ '{{component "some-component" tagName="tfoot"}}
',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ' ',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '{{! this is a comment }}| Header |
| Body |
| Footer |
',
+ '',
+ '',
+ '',
+ `
+
+ {{#if someCondition}}
+
+
+ {{else}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#unless someCondition}}
+
+
+ {{else}}
+
+
+
+ {{/unless}}
+
+ `,
+ ],
+
+ invalid: [
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+
+ {
+ code: `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+ {{#if foo}}
+ 12
+ {{else}}
+ text
+ {{/if}}
+
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+ {{#if foo}}
+ 12
+ {{else}}
+ text
+ {{/if}}
+
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#if foo}}
+ {{else}}
+
+ {{/if}}
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#unless foo}}
+
+
+
+ {{/unless}}
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#if foo}}
+
+
+
+ {{/if}}
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#unless foo}}
+ {{some-component}}
+ {{/unless}}
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: `
+
+ {{#something foo}}
+
+ {{/something}}
+
+ `,
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '{{#each foo as |bar|}}{{bar}}{{/each}}
',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '{{some-component tagName="div"}}
',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '{{some-component otherProp="tbody"}}
',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'ordering' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ messageId: 'ordering' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ messageId: 'ordering' }],
+ },
+ ],
+});
+
+const hbsRuleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser/hbs'),
+ parserOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'module',
+ },
+});
+
+hbsRuleTester.run('template-table-groups', rule, {
+ valid: [
+ `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+
+ {{#if foo}}
+
+
+
+ {{else}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#if foo}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#unless foo}}
+
+
+
+ {{/unless}}
+
+ `,
+ `
+
+ {{#each foo as |bar|}}
+
+ bar
+
+ {{/each}}
+
+ `,
+ `
+
+ {{#each-in foo as |bar|}}
+
+ bar
+
+ {{/each-in}}
+
+ `,
+ `
+
+ {{#let foo as |bar|}}
+
+ bar
+
+ {{/let}}
+
+ `,
+ `
+
+ {{#with foo as |bar|}}
+
+ bar
+
+ {{/with}}
+
+ `,
+ `
+
+ {{#each foo as |bar|}}
+ {{#if bar}}
+ {{#unless baz}}
+
+ bar
+
+ {{/unless}}
+ {{/if}}
+ {{/each}}
+
+ `,
+ '{{some-component tagName="tbody"}}
',
+ '{{some-component tagName="thead"}}
',
+ '{{some-component tagName="tfoot"}}
',
+ '{{#some-component tagName="tbody"}}{{/some-component}}
',
+ '{{#some-component tagName="thead"}}{{/some-component}}
',
+ '{{#some-component tagName="tfoot"}}{{/some-component}}
',
+ '{{component "some-component" tagName="tbody"}}
',
+ '{{component "some-component" tagName="thead"}}
',
+ '{{component "some-component" tagName="tfoot"}}
',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ' ',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '{{! this is a comment }}',
+ '| Header |
',
+ '| Body |
',
+ '| Footer |
',
+ '',
+ '',
+ `
+
+ {{#if someCondition}}
+
+
+ {{else}}
+
+
+
+ {{/if}}
+
+ `,
+ `
+
+ {{#unless someCondition}}
+
+
+ {{else}}
+
+
+
+ {{/unless}}
+
+ `,
+ `
+
+
+
+
+
+
+
+
+ `,
+ '',
+ // allowed-*-components config tests (curly-brace invocation)
+ {
+ code: `
+
+ {{nested/my-caption}}
+ {{nested/my-colgroup}}
+ {{nested/my-thead}}
+ {{nested/my-tbody}}
+ {{nested/my-tfoot}}
+
+ `,
+ options: [
+ {
+ 'allowed-caption-components': ['nested/my-caption'],
+ 'allowed-colgroup-components': ['nested/my-colgroup'],
+ 'allowed-thead-components': ['nested/my-thead'],
+ 'allowed-tbody-components': ['nested/my-tbody'],
+ 'allowed-tfoot-components': ['nested/my-tfoot'],
+ },
+ ],
+ },
+ // allowed-*-components config tests (angle-bracket invocation)
+ {
+ code: `
+
+ `,
+ options: [
+ {
+ 'allowed-caption-components': ['nested/my-caption'],
+ 'allowed-colgroup-components': ['nested/my-colgroup'],
+ 'allowed-thead-components': ['nested/my-thead'],
+ 'allowed-tbody-components': ['nested/my-tbody'],
+ 'allowed-tfoot-components': ['nested/my-tfoot'],
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ options: [
+ {
+ 'allowed-thead-components': ['nested/head-or-foot'],
+ 'allowed-tbody-components': ['nested/body'],
+ 'allowed-tfoot-components': ['nested/head-or-foot'],
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ options: [
+ {
+ 'allowed-caption-components': ['nested/my-caption'],
+ },
+ ],
+ },
+ ],
+ invalid: [
+ {
+ code: `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+ {{#if foo}}
+ 12
+ {{else}}
+ text
+ {{/if}}
+
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#if showCaption}}
+ Some Name
+ {{/if}}
+ {{#if foo}}
+ 12
+ {{else}}
+ text
+ {{/if}}
+
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#if foo}}
+ {{else}}
+
+ {{/if}}
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#unless foo}}
+
+
+
+ {{/unless}}
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#if foo}}
+
+
+
+ {{/if}}
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#unless foo}}
+ {{some-component}}
+ {{/unless}}
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ {{#something foo}}
+
+ {{/something}}
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '{{#each foo as |bar|}}{{bar}}{{/each}}
',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '{{some-component tagName="div"}}
',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '{{some-component otherProp="tbody"}}
',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ {
+ code: '',
+ output: null,
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ options: [
+ {
+ 'allowed-tbody-components': ['nested/my-tbody'],
+ },
+ ],
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ // Config: allowed-*-components invalid tests
+ {
+ code: `
+
+ `,
+ output: null,
+ options: [{ 'allowed-caption-components': ['nested/allowed'] }],
+ errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ options: [
+ {
+ 'allowed-thead-components': ['nested/my-thead'],
+ 'allowed-tfoot-components': ['nested/my-tfoot'],
+ },
+ ],
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ options: [
+ {
+ 'allowed-thead-components': ['nested/head-or-foot'],
+ 'allowed-tbody-components': ['nested/body'],
+ 'allowed-tfoot-components': ['nested/head-or-foot'],
+ },
+ ],
+ errors: [
+ {
+ message:
+ 'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',
+ },
+ ],
+ },
+ ],
+});