Skip to content

Commit 2d41cb9

Browse files
bloveclaude
andauthored
test(cockpit): cover Container & DashboardGrid view components (#148)
* docs(superpowers): plan for genui phase 2 test gap closure Adds unit-test specs for ContainerComponent and DashboardGridComponent — the two views shipped in PR #127 (64373d9) without specs. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * docs(superpowers): fix genui test plan to stub <render-element> The original spec code imported ContainerComponent / DashboardGridComponent directly into TestBed without addressing that <render-element> is itself a standalone component which calls `inject(RENDER_CONTEXT)`. With non-empty childKeys, Angular instantiated the real RenderElementComponent and threw NG0201. Plan now uses TestBed.overrideComponent to swap in a local StubRenderElementComponent that matches the selector and public inputs. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * test(cockpit): cover ContainerComponent layout and child rendering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(cockpit): cover DashboardGridComponent layout and child rendering --------- Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
1 parent 3bda4ba commit 2d41cb9

3 files changed

Lines changed: 462 additions & 0 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: MIT
2+
import { Component, input } from '@angular/core';
3+
import type { Spec } from '@json-render/core';
4+
import { ComponentFixture, TestBed } from '@angular/core/testing';
5+
import { RenderElementComponent } from '@ngaf/render';
6+
import { ContainerComponent } from './container.component';
7+
8+
// Stub matching <render-element>'s selector + public inputs. Swapped into
9+
// ContainerComponent's imports via overrideComponent so Angular doesn't
10+
// instantiate the real RenderElementComponent (which requires RENDER_CONTEXT).
11+
@Component({
12+
selector: 'render-element',
13+
standalone: true,
14+
template: '',
15+
})
16+
class StubRenderElementComponent {
17+
readonly elementKey = input<string>('');
18+
readonly spec = input<Spec | undefined>(undefined);
19+
}
20+
21+
describe('ContainerComponent', () => {
22+
let fixture: ComponentFixture<ContainerComponent>;
23+
24+
// Minimal Spec satisfying the input.required<Spec>() — children resolution
25+
// is delegated to <render-element>, so the spec body itself is not exercised.
26+
const emptySpec: Spec = { elements: {}, root: 'root' };
27+
28+
beforeEach(async () => {
29+
await TestBed.configureTestingModule({
30+
imports: [ContainerComponent],
31+
})
32+
.overrideComponent(ContainerComponent, {
33+
remove: { imports: [RenderElementComponent] },
34+
add: { imports: [StubRenderElementComponent] },
35+
})
36+
.compileComponents();
37+
fixture = TestBed.createComponent(ContainerComponent);
38+
});
39+
40+
it('applies column flex classes by default', () => {
41+
fixture.componentRef.setInput('spec', emptySpec);
42+
fixture.detectChanges();
43+
const wrapper = fixture.nativeElement.querySelector('div');
44+
expect(wrapper?.className).toContain('flex-col');
45+
expect(wrapper?.className).not.toContain('flex-row');
46+
});
47+
48+
it('applies row flex classes when direction is "row"', () => {
49+
fixture.componentRef.setInput('spec', emptySpec);
50+
fixture.componentRef.setInput('direction', 'row');
51+
fixture.detectChanges();
52+
const wrapper = fixture.nativeElement.querySelector('div');
53+
expect(wrapper?.className).toContain('flex-row');
54+
expect(wrapper?.className).not.toContain('flex-col');
55+
});
56+
57+
it('renders one render-element per childKey', () => {
58+
fixture.componentRef.setInput('spec', emptySpec);
59+
fixture.componentRef.setInput('childKeys', ['a', 'b', 'c']);
60+
fixture.detectChanges();
61+
const elements = fixture.nativeElement.querySelectorAll('render-element');
62+
expect(elements.length).toBe(3);
63+
});
64+
65+
it('renders no render-element children when childKeys is empty', () => {
66+
fixture.componentRef.setInput('spec', emptySpec);
67+
fixture.componentRef.setInput('childKeys', []);
68+
fixture.detectChanges();
69+
const elements = fixture.nativeElement.querySelectorAll('render-element');
70+
expect(elements.length).toBe(0);
71+
});
72+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// SPDX-License-Identifier: MIT
2+
import { Component, input } from '@angular/core';
3+
import type { Spec } from '@json-render/core';
4+
import { ComponentFixture, TestBed } from '@angular/core/testing';
5+
import { RenderElementComponent } from '@ngaf/render';
6+
import { DashboardGridComponent } from './dashboard-grid.component';
7+
8+
// See ContainerComponent spec for rationale. Same stub pattern keeps Angular
9+
// from instantiating the real <render-element> (which needs RENDER_CONTEXT).
10+
@Component({
11+
selector: 'render-element',
12+
standalone: true,
13+
template: '',
14+
})
15+
class StubRenderElementComponent {
16+
readonly elementKey = input<string>('');
17+
readonly spec = input<Spec | undefined>(undefined);
18+
}
19+
20+
describe('DashboardGridComponent', () => {
21+
let fixture: ComponentFixture<DashboardGridComponent>;
22+
23+
const emptySpec: Spec = { elements: {}, root: 'root' };
24+
25+
beforeEach(async () => {
26+
await TestBed.configureTestingModule({
27+
imports: [DashboardGridComponent],
28+
})
29+
.overrideComponent(DashboardGridComponent, {
30+
remove: { imports: [RenderElementComponent] },
31+
add: { imports: [StubRenderElementComponent] },
32+
})
33+
.compileComponents();
34+
fixture = TestBed.createComponent(DashboardGridComponent);
35+
});
36+
37+
it('applies vertical flex layout with section spacing', () => {
38+
fixture.componentRef.setInput('spec', emptySpec);
39+
fixture.detectChanges();
40+
const wrapper = fixture.nativeElement.querySelector('div');
41+
expect(wrapper?.className).toContain('flex-col');
42+
expect(wrapper?.className).toContain('gap-6');
43+
});
44+
45+
it('renders one render-element per childKey', () => {
46+
fixture.componentRef.setInput('spec', emptySpec);
47+
fixture.componentRef.setInput('childKeys', ['stats_row', 'charts_row', 'table_section']);
48+
fixture.detectChanges();
49+
const elements = fixture.nativeElement.querySelectorAll('render-element');
50+
expect(elements.length).toBe(3);
51+
});
52+
53+
it('renders no render-element children when childKeys is empty', () => {
54+
fixture.componentRef.setInput('spec', emptySpec);
55+
fixture.componentRef.setInput('childKeys', []);
56+
fixture.detectChanges();
57+
const elements = fixture.nativeElement.querySelectorAll('render-element');
58+
expect(elements.length).toBe(0);
59+
});
60+
});

0 commit comments

Comments
 (0)