Skip to content

Commit a94aac0

Browse files
authored
Merge pull request #1076 from microting/copilot/fix-broken-unit-tests
Fix broken unit tests by converting Jasmine to Jest syntax
2 parents 276d11d + a83b2b7 commit a94aac0

File tree

6 files changed

+215
-68
lines changed

6 files changed

+215
-68
lines changed

JEST_CONVERSION.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Jest Conversion Summary
2+
3+
## Overview
4+
All unit test spec files in the time-planning-pn plugin have been converted from Jasmine to Jest syntax to fix broken unit tests.
5+
6+
## Files Converted
7+
8+
### Component Specs
9+
1. **time-plannings-container.component.spec.ts**
10+
- Converted all Jasmine spy objects to Jest mocked objects
11+
- Updated mock creation from `jasmine.createSpyObj` to manual object creation with `jest.fn()`
12+
- Changed `spyOn` to `jest.spyOn`
13+
- Changed `.and.returnValue()` to `.mockReturnValue()`
14+
15+
2. **time-plannings-table.component.spec.ts**
16+
- Converted Jasmine SpyObj to Jest Mocked types
17+
- Updated all mock methods to use Jest syntax
18+
- Converted spy creation and return value setting
19+
20+
3. **assigned-site-dialog.component.spec.ts**
21+
- Converted service and store mocks to Jest syntax
22+
- Updated mock return value patterns
23+
24+
4. **workday-entity-dialog.component.spec.ts**
25+
- Converted large component spec with extensive time conversion tests
26+
- Updated all mock patterns to Jest
27+
28+
5. **download-excel-dialog.component.spec.ts**
29+
- Converted service mocks to Jest
30+
- Updated Jasmine's `.calls.mostRecent().args[0]` to Jest's `.mock.calls[.mock.calls.length - 1][0]`
31+
32+
### Service Specs
33+
1. **time-planning-pn-plannings.service.spec.ts**
34+
- Already using Jest syntax (no changes needed)
35+
36+
## Conversion Patterns
37+
38+
### Type Declarations
39+
```typescript
40+
// Before (Jasmine)
41+
let mockService: jasmine.SpyObj<ServiceType>;
42+
43+
// After (Jest)
44+
let mockService: jest.Mocked<ServiceType>;
45+
```
46+
47+
### Mock Creation
48+
```typescript
49+
// Before (Jasmine)
50+
mockService = jasmine.createSpyObj('ServiceType', ['method1', 'method2']);
51+
52+
// After (Jest)
53+
mockService = {
54+
method1: jest.fn(),
55+
method2: jest.fn(),
56+
} as any;
57+
```
58+
59+
### Return Values
60+
```typescript
61+
// Before (Jasmine)
62+
mockService.method1.and.returnValue(of(data));
63+
64+
// After (Jest)
65+
mockService.method1.mockReturnValue(of(data));
66+
```
67+
68+
### Spying on Methods
69+
```typescript
70+
// Before (Jasmine)
71+
spyOn(component, 'methodName');
72+
73+
// After (Jest)
74+
jest.spyOn(component, 'methodName');
75+
```
76+
77+
### Accessing Call Arguments
78+
```typescript
79+
// Before (Jasmine)
80+
const args = mockService.method.calls.mostRecent().args[0];
81+
82+
// After (Jest)
83+
const args = mockService.method.mock.calls[mockService.method.mock.calls.length - 1][0];
84+
```
85+
86+
## Testing Command
87+
To run the unit tests for the time-planning-pn plugin, use:
88+
89+
```bash
90+
npm run test:unit -- --testPathPatterns=time-planning-pn --coverage --collectCoverageFrom='src/app/plugins/modules/time-planning-pn/**/*.ts' --coveragePathIgnorePatterns='\.spec\.ts'
91+
```
92+
93+
## Benefits of Jest Conversion
94+
95+
1. **Consistency**: All test files now use the same testing framework (Jest)
96+
2. **Modern Syntax**: Jest is more commonly used in modern JavaScript/TypeScript projects
97+
3. **Better Performance**: Jest offers better performance with parallel test execution
98+
4. **Built-in Mocking**: Jest has powerful built-in mocking capabilities without additional libraries
99+
100+
## Verification
101+
102+
All files have been verified to:
103+
- ✅ Use Jest mocking syntax exclusively
104+
- ✅ Have no remaining Jasmine patterns (jasmine.SpyObj, jasmine.createSpyObj, .and.returnValue)
105+
- ✅ Use proper Jest spy and mock patterns
106+
- ✅ Maintain all original test logic and assertions
107+
- ✅ Preserve test coverage for time conversion utilities, component interactions, and service calls
108+
109+
## Notes
110+
111+
- No test logic was changed during conversion
112+
- All assertions remain identical
113+
- Test descriptions and structure are preserved
114+
- The conversion maintains 100% backward compatibility with the original test intent

eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.spec.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { of } from 'rxjs';
99
describe('AssignedSiteDialogComponent', () => {
1010
let component: AssignedSiteDialogComponent;
1111
let fixture: ComponentFixture<AssignedSiteDialogComponent>;
12-
let mockSettingsService: jasmine.SpyObj<TimePlanningPnSettingsService>;
13-
let mockStore: jasmine.SpyObj<Store>;
12+
let mockSettingsService: jest.Mocked<TimePlanningPnSettingsService>;
13+
let mockStore: jest.Mocked<Store>;
1414

1515
const mockAssignedSiteData = {
1616
id: 1,
@@ -50,15 +50,17 @@ describe('AssignedSiteDialogComponent', () => {
5050
};
5151

5252
beforeEach(async () => {
53-
mockSettingsService = jasmine.createSpyObj('TimePlanningPnSettingsService', [
54-
'getGlobalAutoBreakCalculationSettings',
55-
'updateAssignedSite',
56-
'getAssignedSite'
57-
]);
58-
mockStore = jasmine.createSpyObj('Store', ['select']);
53+
mockSettingsService = {
54+
getGlobalAutoBreakCalculationSettings: jest.fn(),
55+
updateAssignedSite: jest.fn(),
56+
getAssignedSite: jest.fn(),
57+
} as any;
58+
mockStore = {
59+
select: jest.fn(),
60+
} as any;
5961

60-
mockStore.select.and.returnValue(of(true));
61-
mockSettingsService.getGlobalAutoBreakCalculationSettings.and.returnValue(
62+
mockStore.select.mockReturnValue(of(true));
63+
mockSettingsService.getGlobalAutoBreakCalculationSettings.mockReturnValue(
6264
of({ success: true, model: {} }) as any
6365
);
6466

@@ -207,7 +209,7 @@ describe('AssignedSiteDialogComponent', () => {
207209
describe('Break Settings', () => {
208210
beforeEach(() => {
209211
component.ngOnInit();
210-
mockSettingsService.getGlobalAutoBreakCalculationSettings.and.returnValue(
212+
mockSettingsService.getGlobalAutoBreakCalculationSettings.mockReturnValue(
211213
of({
212214
success: true,
213215
model: {

eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ import { format } from 'date-fns';
99
describe('DownloadExcelDialogComponent', () => {
1010
let component: DownloadExcelDialogComponent;
1111
let fixture: ComponentFixture<DownloadExcelDialogComponent>;
12-
let mockWorkingHoursService: jasmine.SpyObj<TimePlanningPnWorkingHoursService>;
13-
let mockToastrService: jasmine.SpyObj<ToastrService>;
12+
let mockWorkingHoursService: jest.Mocked<TimePlanningPnWorkingHoursService>;
13+
let mockToastrService: jest.Mocked<ToastrService>;
1414

1515
beforeEach(async () => {
16-
mockWorkingHoursService = jasmine.createSpyObj('TimePlanningPnWorkingHoursService', [
17-
'downloadReport',
18-
'downloadReportAllWorkers'
19-
]);
20-
mockToastrService = jasmine.createSpyObj('ToastrService', ['error', 'success']);
16+
mockWorkingHoursService = {
17+
downloadReport: jest.fn(),
18+
downloadReportAllWorkers: jest.fn(),
19+
} as any;
20+
mockToastrService = {
21+
error: jest.fn(),
22+
success: jest.fn(),
23+
} as any;
2124

2225
await TestBed.configureTestingModule({
2326
declarations: [DownloadExcelDialogComponent],
@@ -75,7 +78,7 @@ describe('DownloadExcelDialogComponent', () => {
7578

7679
it('should call downloadReport with correct model', () => {
7780
const mockBlob = new Blob(['test'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
78-
mockWorkingHoursService.downloadReport.and.returnValue(of(mockBlob));
81+
mockWorkingHoursService.downloadReport.mockReturnValue(of(mockBlob));
7982

8083
component.onDownloadExcelReport();
8184

@@ -87,7 +90,7 @@ describe('DownloadExcelDialogComponent', () => {
8790
});
8891

8992
it('should show error toast when download fails', (done) => {
90-
mockWorkingHoursService.downloadReport.and.returnValue(
93+
mockWorkingHoursService.downloadReport.mockReturnValue(
9194
throwError(() => new Error('Download failed'))
9295
);
9396

@@ -109,7 +112,7 @@ describe('DownloadExcelDialogComponent', () => {
109112

110113
it('should call downloadReportAllWorkers with correct model', () => {
111114
const mockBlob = new Blob(['test'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
112-
mockWorkingHoursService.downloadReportAllWorkers.and.returnValue(of(mockBlob));
115+
mockWorkingHoursService.downloadReportAllWorkers.mockReturnValue(of(mockBlob));
113116

114117
component.onDownloadExcelReportAllWorkers();
115118

@@ -120,7 +123,7 @@ describe('DownloadExcelDialogComponent', () => {
120123
});
121124

122125
it('should show error toast when download all workers fails', (done) => {
123-
mockWorkingHoursService.downloadReportAllWorkers.and.returnValue(
126+
mockWorkingHoursService.downloadReportAllWorkers.mockReturnValue(
124127
throwError(() => new Error('Download failed'))
125128
);
126129

@@ -135,12 +138,12 @@ describe('DownloadExcelDialogComponent', () => {
135138

136139
it('should not include siteId in all workers report model', () => {
137140
const mockBlob = new Blob(['test'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
138-
mockWorkingHoursService.downloadReportAllWorkers.and.returnValue(of(mockBlob));
141+
mockWorkingHoursService.downloadReportAllWorkers.mockReturnValue(of(mockBlob));
139142
component.siteId = 999; // Should not be included
140143

141144
component.onDownloadExcelReportAllWorkers();
142145

143-
const callArgs = mockWorkingHoursService.downloadReportAllWorkers.calls.mostRecent().args[0];
146+
const callArgs = mockWorkingHoursService.downloadReportAllWorkers.mock.calls[mockWorkingHoursService.downloadReportAllWorkers.mock.calls.length - 1][0];
144147
expect('siteId' in callArgs).toBe(false);
145148
});
146149
});

eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.spec.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { of } from 'rxjs';
1010
describe('WorkdayEntityDialogComponent', () => {
1111
let component: WorkdayEntityDialogComponent;
1212
let fixture: ComponentFixture<WorkdayEntityDialogComponent>;
13-
let mockPlanningsService: jasmine.SpyObj<TimePlanningPnPlanningsService>;
14-
let mockTranslateService: jasmine.SpyObj<TranslateService>;
13+
let mockPlanningsService: jest.Mocked<TimePlanningPnPlanningsService>;
14+
let mockTranslateService: jest.Mocked<TranslateService>;
1515

1616
const mockData = {
1717
planningPrDayModels: {
@@ -82,13 +82,17 @@ describe('WorkdayEntityDialogComponent', () => {
8282
};
8383

8484
beforeEach(async () => {
85-
mockPlanningsService = jasmine.createSpyObj('TimePlanningPnPlanningsService', ['updatePlanning']);
86-
mockTranslateService = jasmine.createSpyObj('TranslateService', ['instant', 'stream'], {
87-
onLangChange: of({ lang: 'en' })
88-
});
89-
90-
mockTranslateService.instant.and.returnValue('Translated');
91-
mockTranslateService.stream.and.returnValue(of('Translated'));
85+
mockPlanningsService = {
86+
updatePlanning: jest.fn(),
87+
} as any;
88+
mockTranslateService = {
89+
instant: jest.fn(),
90+
stream: jest.fn(),
91+
onLangChange: of({ lang: 'en' }),
92+
} as any;
93+
94+
mockTranslateService.instant.mockReturnValue('Translated');
95+
mockTranslateService.stream.mockReturnValue(of('Translated'));
9296

9397
await TestBed.configureTestingModule({
9498
declarations: [WorkdayEntityDialogComponent],

eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.spec.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,32 @@ import { format } from 'date-fns';
1010
describe('TimePlanningsContainerComponent', () => {
1111
let component: TimePlanningsContainerComponent;
1212
let fixture: ComponentFixture<TimePlanningsContainerComponent>;
13-
let mockPlanningsService: jasmine.SpyObj<TimePlanningPnPlanningsService>;
14-
let mockSettingsService: jasmine.SpyObj<TimePlanningPnSettingsService>;
15-
let mockDialog: jasmine.SpyObj<MatDialog>;
16-
let mockStore: jasmine.SpyObj<Store>;
13+
let mockPlanningsService: jest.Mocked<TimePlanningPnPlanningsService>;
14+
let mockSettingsService: jest.Mocked<TimePlanningPnSettingsService>;
15+
let mockDialog: jest.Mocked<MatDialog>;
16+
let mockStore: jest.Mocked<Store>;
1717

1818
beforeEach(async () => {
19-
mockPlanningsService = jasmine.createSpyObj('TimePlanningPnPlanningsService', ['getPlannings', 'updatePlanning']);
20-
mockSettingsService = jasmine.createSpyObj('TimePlanningPnSettingsService', ['getAvailableSites', 'getResignedSites', 'getAssignedSite', 'updateAssignedSite']);
21-
mockDialog = jasmine.createSpyObj('MatDialog', ['open']);
22-
mockStore = jasmine.createSpyObj('Store', ['select']);
23-
24-
mockStore.select.and.returnValue(of('en-US'));
25-
mockSettingsService.getAvailableSites.and.returnValue(of({ success: true, model: [] }) as any);
26-
mockPlanningsService.getPlannings.and.returnValue(of({ success: true, model: [] }) as any);
19+
mockPlanningsService = {
20+
getPlannings: jest.fn(),
21+
updatePlanning: jest.fn(),
22+
} as any;
23+
mockSettingsService = {
24+
getAvailableSites: jest.fn(),
25+
getResignedSites: jest.fn(),
26+
getAssignedSite: jest.fn(),
27+
updateAssignedSite: jest.fn(),
28+
} as any;
29+
mockDialog = {
30+
open: jest.fn(),
31+
} as any;
32+
mockStore = {
33+
select: jest.fn(),
34+
} as any;
35+
36+
mockStore.select.mockReturnValue(of('en-US'));
37+
mockSettingsService.getAvailableSites.mockReturnValue(of({ success: true, model: [] }) as any);
38+
mockPlanningsService.getPlannings.mockReturnValue(of({ success: true, model: [] }) as any);
2739

2840
await TestBed.configureTestingModule({
2941
declarations: [TimePlanningsContainerComponent],
@@ -105,23 +117,23 @@ describe('TimePlanningsContainerComponent', () => {
105117

106118
describe('Event Handlers', () => {
107119
it('should call getPlannings when onTimePlanningChanged is triggered', () => {
108-
spyOn(component, 'getPlannings');
120+
jest.spyOn(component, 'getPlannings');
109121

110122
component.onTimePlanningChanged({});
111123

112124
expect(component.getPlannings).toHaveBeenCalled();
113125
});
114126

115127
it('should call getPlannings when onAssignedSiteChanged is triggered', () => {
116-
spyOn(component, 'getPlannings');
128+
jest.spyOn(component, 'getPlannings');
117129

118130
component.onAssignedSiteChanged({});
119131

120132
expect(component.getPlannings).toHaveBeenCalled();
121133
});
122134

123135
it('should update siteId and call getPlannings when onSiteChanged is triggered', () => {
124-
spyOn(component, 'getPlannings');
136+
jest.spyOn(component, 'getPlannings');
125137
const testSiteId = 123;
126138

127139
component.onSiteChanged(testSiteId);
@@ -135,7 +147,7 @@ describe('TimePlanningsContainerComponent', () => {
135147
it('should open download excel dialog with available sites', () => {
136148
component.availableSites = [{ id: 1, name: 'Test Site' } as any];
137149
const mockDialogRef = { afterClosed: () => of(null) };
138-
mockDialog.open.and.returnValue(mockDialogRef as any);
150+
mockDialog.open.mockReturnValue(mockDialogRef as any);
139151

140152
component.openDownloadExcelDialog();
141153

@@ -145,7 +157,7 @@ describe('TimePlanningsContainerComponent', () => {
145157

146158
describe('Show Resigned Sites', () => {
147159
it('should load resigned sites when showResignedSites is true', () => {
148-
mockSettingsService.getResignedSites.and.returnValue(of({ success: true, model: [{ id: 1, name: 'Resigned Site' }] } as any));
160+
mockSettingsService.getResignedSites.mockReturnValue(of({ success: true, model: [{ id: 1, name: 'Resigned Site' }] } as any));
149161

150162
component.onShowResignedSitesChanged({ checked: true });
151163

@@ -155,7 +167,7 @@ describe('TimePlanningsContainerComponent', () => {
155167

156168
it('should load available sites when showResignedSites is false', () => {
157169
component.showResignedSites = true;
158-
mockSettingsService.getAvailableSites.and.returnValue(of({ success: true, model: [{ id: 1, name: 'Available Site' }] } as any));
170+
mockSettingsService.getAvailableSites.mockReturnValue(of({ success: true, model: [{ id: 1, name: 'Available Site' }] } as any));
159171

160172
component.onShowResignedSitesChanged({ checked: false });
161173

0 commit comments

Comments
 (0)