Skip to content

Commit f86eecb

Browse files
guguclaude
andcommitted
Add meaningful tests to 7 component spec files
Previously these test files only had a single "should create" test. Added tests for: - db-table-ai-panel: thread creation, messaging, keyboard events, error handling - widget: initialization, ngOnChanges, output emitters, docs URLs - sso: SAML config fetching, create/update operations, router navigation - filter json-editor: code model initialization, editor options, value handling - record-edit code: language from widget params, theme from UiSettingsService - record-edit json-editor: JSON stringification, null/undefined handling - record-edit markdown: markdown language setup, theme configuration Test count increased from 825 to 883 (+58 tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1105a7b commit f86eecb

7 files changed

Lines changed: 710 additions & 220 deletions

File tree

frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.spec.ts

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,166 @@ import { MatIconTestingModule } from '@angular/material/icon/testing';
44
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
55
import { Angulartics2Module } from 'angulartics2';
66
import { MarkdownService } from 'ngx-markdown';
7+
import { of, throwError } from 'rxjs';
8+
import { ConnectionsService } from 'src/app/services/connections.service';
9+
import { TableStateService } from 'src/app/services/table-state.service';
10+
import { TablesService } from 'src/app/services/tables.service';
711
import { DbTableAiPanelComponent } from './db-table-ai-panel.component';
812

913
describe('DbTableAiPanelComponent', () => {
1014
let component: DbTableAiPanelComponent;
1115
let fixture: ComponentFixture<DbTableAiPanelComponent>;
16+
let tablesService: TablesService;
17+
let tableStateService: TableStateService;
1218

1319
const mockMarkdownService = {
1420
parse: vi.fn().mockReturnValue('parsed markdown'),
1521
};
1622

23+
const mockConnectionsService = {
24+
currentConnectionID: '12345678',
25+
};
26+
27+
const mockTablesService = {
28+
currentTableName: 'users',
29+
createAIthread: vi.fn(),
30+
requestAImessage: vi.fn(),
31+
};
32+
33+
const mockTableStateService = {
34+
aiPanelCast: of(false),
35+
handleViewAIpanel: vi.fn(),
36+
};
37+
1738
beforeEach(async () => {
1839
await TestBed.configureTestingModule({
1940
imports: [Angulartics2Module.forRoot(), DbTableAiPanelComponent, BrowserAnimationsModule, MatIconTestingModule],
20-
providers: [provideHttpClient(), { provide: MarkdownService, useValue: mockMarkdownService }],
41+
providers: [
42+
provideHttpClient(),
43+
{ provide: MarkdownService, useValue: mockMarkdownService },
44+
{ provide: ConnectionsService, useValue: mockConnectionsService },
45+
{ provide: TablesService, useValue: mockTablesService },
46+
{ provide: TableStateService, useValue: mockTableStateService },
47+
],
2148
}).compileComponents();
2249

2350
fixture = TestBed.createComponent(DbTableAiPanelComponent);
2451
component = fixture.componentInstance;
52+
tablesService = TestBed.inject(TablesService);
53+
tableStateService = TestBed.inject(TableStateService);
2554
fixture.detectChanges();
2655
});
2756

2857
it('should create', () => {
2958
expect(component).toBeTruthy();
3059
});
60+
61+
it('should initialize with connection ID and table name', () => {
62+
expect(component.connectionID).toBe('12345678');
63+
expect(component.tableName).toBe('users');
64+
});
65+
66+
it('should have default AI request suggestions', () => {
67+
expect(component.aiRequestSuggestions.length).toBeGreaterThan(0);
68+
expect(component.aiRequestSuggestions).toContain('How many records were created last month?');
69+
});
70+
71+
it('should update character count on keydown', () => {
72+
component.message = 'Hello';
73+
const event = new KeyboardEvent('keydown', { key: 'a' });
74+
component.onKeydown(event);
75+
expect(component.charactrsNumber).toBe(6);
76+
});
77+
78+
it('should create thread on Enter key when no thread exists', () => {
79+
mockTablesService.createAIthread.mockReturnValue(of({ threadId: 'thread-123', responseMessage: 'AI response' }));
80+
81+
component.message = 'Test message';
82+
component.threadID = null;
83+
84+
const event = new KeyboardEvent('keydown', { key: 'Enter' });
85+
Object.defineProperty(event, 'preventDefault', { value: vi.fn() });
86+
component.onKeydown(event);
87+
88+
expect(mockTablesService.createAIthread).toHaveBeenCalledWith('12345678', 'users', 'Test message');
89+
});
90+
91+
it('should send message on Enter key when thread exists', () => {
92+
mockTablesService.requestAImessage.mockReturnValue(of('AI response'));
93+
94+
component.message = 'Follow up message';
95+
component.threadID = 'existing-thread';
96+
97+
const event = new KeyboardEvent('keydown', { key: 'Enter' });
98+
Object.defineProperty(event, 'preventDefault', { value: vi.fn() });
99+
component.onKeydown(event);
100+
101+
expect(mockTablesService.requestAImessage).toHaveBeenCalledWith(
102+
'12345678',
103+
'users',
104+
'existing-thread',
105+
'Follow up message',
106+
);
107+
});
108+
109+
it('should add user message to chain when creating thread', () => {
110+
mockTablesService.createAIthread.mockReturnValue(of({ threadId: 'thread-123', responseMessage: 'AI response' }));
111+
112+
component.message = 'User question';
113+
component.createThread();
114+
115+
expect(component.messagesChain[0]).toEqual({
116+
type: 'user',
117+
text: 'User question',
118+
});
119+
});
120+
121+
it('should add AI response to chain after thread creation', () => {
122+
mockTablesService.createAIthread.mockReturnValue(of({ threadId: 'thread-123', responseMessage: 'AI response' }));
123+
124+
component.message = 'User question';
125+
component.createThread();
126+
127+
expect(component.threadID).toBe('thread-123');
128+
expect(component.messagesChain[1]).toEqual({
129+
type: 'ai',
130+
text: 'parsed markdown',
131+
});
132+
});
133+
134+
it('should handle error when creating thread', () => {
135+
mockTablesService.createAIthread.mockReturnValue(throwError(() => 'Error message'));
136+
137+
component.message = 'User question';
138+
component.createThread();
139+
140+
expect(component.messagesChain[1]).toEqual({
141+
type: 'ai-error',
142+
text: 'Error message',
143+
});
144+
});
145+
146+
it('should use suggested message when provided to createThread', () => {
147+
mockTablesService.createAIthread.mockReturnValue(of({ threadId: 'thread-123', responseMessage: 'AI response' }));
148+
149+
component.createThread('Suggested question');
150+
151+
expect(component.messagesChain[0].text).toBe('Suggested question');
152+
});
153+
154+
it('should call handleViewAIpanel when closing', () => {
155+
component.handleClose();
156+
expect(mockTableStateService.handleViewAIpanel).toHaveBeenCalled();
157+
});
158+
159+
it('should clear message after sending', () => {
160+
mockTablesService.requestAImessage.mockReturnValue(of('AI response'));
161+
162+
component.message = 'Test message';
163+
component.threadID = 'thread-123';
164+
component.sendMessage();
165+
166+
expect(component.message).toBe('');
167+
expect(component.charactrsNumber).toBe(0);
168+
});
31169
});
Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,109 @@
1+
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
12
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { NO_ERRORS_SCHEMA } from '@angular/core';
3-
4-
import { WidgetComponent } from './widget.component';
53
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
6-
import { MockCodeEditorComponent } from 'src/app/testing/code-editor.mock';
74
import { CodeEditorModule } from '@ngstack/code-editor';
5+
import { MockCodeEditorComponent } from 'src/app/testing/code-editor.mock';
6+
import { WidgetComponent } from './widget.component';
87

98
describe('WidgetComponent', () => {
10-
let component: WidgetComponent;
11-
let fixture: ComponentFixture<WidgetComponent>;
12-
13-
beforeEach(async () => {
14-
await TestBed.configureTestingModule({
15-
imports: [WidgetComponent, BrowserAnimationsModule]
16-
})
17-
.overrideComponent(WidgetComponent, {
18-
remove: { imports: [CodeEditorModule] },
19-
add: { imports: [MockCodeEditorComponent], schemas: [NO_ERRORS_SCHEMA] }
20-
})
21-
.compileComponents();
22-
23-
fixture = TestBed.createComponent(WidgetComponent);
24-
component = fixture.componentInstance;
25-
26-
component.widget = {
27-
field_name: 'password',
28-
widget_type: "Password",
29-
widget_params: "",
30-
name: "User Password",
31-
description: ""
32-
}
33-
34-
fixture.detectChanges();
35-
});
36-
37-
it('should create', () => {
38-
expect(component).toBeTruthy();
39-
});
9+
let component: WidgetComponent;
10+
let fixture: ComponentFixture<WidgetComponent>;
11+
12+
const mockWidget = {
13+
field_name: 'password',
14+
widget_type: 'Password',
15+
widget_params: '{"minLength": 8}',
16+
name: 'User Password',
17+
description: 'Password field',
18+
};
19+
20+
beforeEach(async () => {
21+
await TestBed.configureTestingModule({
22+
imports: [WidgetComponent, BrowserAnimationsModule],
23+
})
24+
.overrideComponent(WidgetComponent, {
25+
remove: { imports: [CodeEditorModule] },
26+
add: { imports: [MockCodeEditorComponent], schemas: [NO_ERRORS_SCHEMA] },
27+
})
28+
.compileComponents();
29+
30+
fixture = TestBed.createComponent(WidgetComponent);
31+
component = fixture.componentInstance;
32+
33+
component.widget = { ...mockWidget };
34+
component.index = 0;
35+
component.fields = ['email', 'username', 'password'];
36+
component.widgetTypes = ['Default', 'Password', 'Textarea', 'Code'];
37+
38+
fixture.detectChanges();
39+
});
40+
41+
it('should create', () => {
42+
expect(component).toBeTruthy();
43+
});
44+
45+
it('should initialize mutableWidgetParams on init', () => {
46+
expect(component.mutableWidgetParams).toEqual({
47+
language: 'json',
48+
uri: 'widget-params-0.json',
49+
value: '{"minLength": 8}',
50+
});
51+
});
52+
53+
it('should have correct code editor options', () => {
54+
expect(component.paramsEditorOptions.minimap.enabled).toBe(false);
55+
expect(component.paramsEditorOptions.wordWrap).toBe('on');
56+
expect(component.paramsEditorOptions.automaticLayout).toBe(true);
57+
});
58+
59+
it('should have documentation URLs for widget types', () => {
60+
expect(component.docsUrls.Password).toContain('docs.rocketadmin.com');
61+
expect(component.docsUrls.Boolean).toContain('widgets_management#boolean');
62+
expect(component.docsUrls.Code).toContain('widgets_management#code');
63+
});
64+
65+
it('should emit onSelectWidgetField when field is selected', () => {
66+
const emitSpy = vi.spyOn(component.onSelectWidgetField, 'emit');
67+
component.onSelectWidgetField.emit('email');
68+
expect(emitSpy).toHaveBeenCalledWith('email');
69+
});
70+
71+
it('should emit onWidgetTypeChange when widget type changes', () => {
72+
const emitSpy = vi.spyOn(component.onWidgetTypeChange, 'emit');
73+
component.onWidgetTypeChange.emit('Textarea');
74+
expect(emitSpy).toHaveBeenCalledWith('Textarea');
75+
});
76+
77+
it('should emit onWidgetDelete when delete is triggered', () => {
78+
const emitSpy = vi.spyOn(component.onWidgetDelete, 'emit');
79+
component.onWidgetDelete.emit('password');
80+
expect(emitSpy).toHaveBeenCalledWith('password');
81+
});
82+
83+
it('should emit onWidgetParamsChange with value and fieldName', () => {
84+
const emitSpy = vi.spyOn(component.onWidgetParamsChange, 'emit');
85+
component.onWidgetParamsChange.emit({ value: '{"new": "params"}', fieldName: 'password' });
86+
expect(emitSpy).toHaveBeenCalledWith({ value: '{"new": "params"}', fieldName: 'password' });
87+
});
88+
89+
it('should update mutableWidgetParams when widgetType changes', () => {
90+
const newParams = '{"newParam": true}';
91+
component.widget = { ...mockWidget, widget_params: newParams };
92+
93+
component.ngOnChanges({
94+
widgetType: new SimpleChange('Password', 'Textarea', false),
95+
});
96+
97+
expect(component.mutableWidgetParams.value).toBe(newParams);
98+
});
99+
100+
it('should not update mutableWidgetParams when widgetType does not change', () => {
101+
const originalValue = component.mutableWidgetParams.value;
102+
103+
component.ngOnChanges({
104+
someOtherProperty: new SimpleChange('old', 'new', false),
105+
});
106+
107+
expect(component.mutableWidgetParams.value).toBe(originalValue);
108+
});
40109
});

0 commit comments

Comments
 (0)