Add guidelines for creating and maintaining tests in .aiassistant/rules/Creating and maintaining tests.md

- Introduced detailed instructions for test development using WebdriverIO, covering structure, naming conventions, and best practices.
- Added examples for browser interactions, assertions, and handling internationalization (i18n) in tests.
- Documented prerequisites for mobile testing and best practices for maintaining test cases.

Enhance SelfServeTasks capabilities by introducing `services` management

- Added `services` property to `SelfServeTasks` for task-specific service handling.
- Implemented logic to determine and update allowed services dynamically based on active tasks.
- Extended machine availability check (`isMachineAvailable`) to consider services allowed per task.

Expand backend support for lane services

- Added new endpoints to manage lane services (`/modules/self-serve/lane/services/allowed` and `/modules/self-serve/lane/relay/machine/enable`).
- Extended OpenAPI specifications to include service configuration for tasks and validation of lane services.

Modify SelfServe UI components for displaying allowed services

- Updated multiple screens to show allowed services based on active tasks.
- Added tags for visualizing allowed task services in `SelfServeTryModal` and `SelfServeTasksTable`.

Add `i18n-settings-wheel.spec.js` for translation key verification

- Added new test suite to ensure i18n translation parity between English and Danish locale files for `settings_wheel`.
- Implemented checks to validate key presence and exact match across both languages.
This commit is contained in:
Jeppe Bundgaard
2026-02-18 14:12:12 +01:00
parent 21b222674a
commit a29b978bc5
8 changed files with 713 additions and 17 deletions
+175
View File
@@ -0,0 +1,175 @@
/**
* Test to verify i18n translations for ActionSettingsWheelButton component
* are properly defined in both English and Danish locale files.
*/
describe('i18n Settings Wheel Translations', () => {
const settingsWheelKeys = [
'close',
'change_password',
'enter_password',
'please_enter_password',
'password_changed',
'password_changed_text',
'error',
'error_changing_password',
'scan_qr_to_login',
'copy_link',
'link_copied',
'booking',
'mark_as_completed',
'view_booking_new_tab',
'change_association',
'associate_order',
'change_order',
'delete_booking',
'view_lane_new_tab',
'view_order_new_tab',
'attach_wash_certificate',
'change_customer',
'change_invoice_collection',
'download_invoice',
'delete_order',
'view_invoice_collection_new_tab',
'view_vehicle_new_tab',
'view_customer_new_tab',
'login_as_customer',
'login_as_user',
'login_as_user_qr',
'show_qr_code',
'show_customer',
'edit_permissions',
'delete_user',
'attached_files',
'open_attached_file',
'error_downloading_attachment',
'no_actions_defined',
'view_lane_setup_new_tab'
];
it('should have all settings_wheel translations defined in English', async () => {
await browser.url('/');
const result = await browser.executeAsync(async (keys, done) => {
try {
const response = await fetch('/src/i18n/locales/en.json');
const data = await response.json();
// Get the top-level keys to understand structure
const topKeys = Object.keys(data);
// Check if admin exists
if (!data.admin) {
done({ success: false, error: 'admin namespace not found. Top keys: ' + topKeys.slice(0, 10).join(', ') });
return;
}
if (!data.admin.pos) {
done({ success: false, error: 'admin.pos not found. Admin keys: ' + Object.keys(data.admin).slice(0, 10).join(', ') });
return;
}
if (!data.admin.pos.settings_wheel) {
done({ success: false, error: 'admin.pos.settings_wheel not found. Pos keys: ' + Object.keys(data.admin.pos).slice(0, 15).join(', ') });
return;
}
const missingKeys = [];
for (const key of keys) {
if (!data.admin.pos.settings_wheel[key]) {
missingKeys.push(key);
}
}
if (missingKeys.length > 0) {
done({ success: false, error: 'Missing keys: ' + missingKeys.join(', ') });
} else {
done({ success: true, keyCount: Object.keys(data.admin.pos.settings_wheel).length });
}
} catch (e) {
done({ success: false, error: 'Exception: ' + e.message });
}
}, settingsWheelKeys);
console.log('English result:', JSON.stringify(result));
expect(result.success).toBe(true);
});
it('should have all settings_wheel translations defined in Danish', async () => {
await browser.url('/');
const result = await browser.executeAsync(async (keys, done) => {
try {
const response = await fetch('/src/i18n/locales/da.json');
const data = await response.json();
// Get the top-level keys to understand structure
const topKeys = Object.keys(data);
// Check if admin exists
if (!data.admin) {
done({ success: false, error: 'admin namespace not found. Top keys: ' + topKeys.slice(0, 10).join(', ') });
return;
}
if (!data.admin.pos) {
done({ success: false, error: 'admin.pos not found. Admin keys: ' + Object.keys(data.admin).slice(0, 10).join(', ') });
return;
}
if (!data.admin.pos.settings_wheel) {
done({ success: false, error: 'admin.pos.settings_wheel not found. Pos keys: ' + Object.keys(data.admin.pos).slice(0, 15).join(', ') });
return;
}
const missingKeys = [];
for (const key of keys) {
if (!data.admin.pos.settings_wheel[key]) {
missingKeys.push(key);
}
}
if (missingKeys.length > 0) {
done({ success: false, error: 'Missing keys: ' + missingKeys.join(', ') });
} else {
done({ success: true, keyCount: Object.keys(data.admin.pos.settings_wheel).length });
}
} catch (e) {
done({ success: false, error: 'Exception: ' + e.message });
}
}, settingsWheelKeys);
console.log('Danish result:', JSON.stringify(result));
expect(result.success).toBe(true);
});
it('should have the same keys in both locales', async () => {
await browser.url('/');
const result = await browser.executeAsync(async (done) => {
try {
const enResponse = await fetch('/src/i18n/locales/en.json');
const en = await enResponse.json();
const daResponse = await fetch('/src/i18n/locales/da.json');
const da = await daResponse.json();
if (!en.admin?.pos?.settings_wheel || !da.admin?.pos?.settings_wheel) {
done({ success: false, error: 'settings_wheel not found in one or both locales' });
return;
}
const enKeys = Object.keys(en.admin.pos.settings_wheel).sort();
const daKeys = Object.keys(da.admin.pos.settings_wheel).sort();
const match = JSON.stringify(enKeys) === JSON.stringify(daKeys);
done({ success: match, enCount: enKeys.length, daCount: daKeys.length });
} catch (e) {
done({ success: false, error: 'Exception: ' + e.message });
}
});
console.log('Parity result:', JSON.stringify(result));
expect(result.success).toBe(true);
});
});