This commit upgrades TypeScript to the latest version 5.3 and introduces `verbatimModuleSyntax` in line with the official Vue guide recommendatinos (vuejs/docs#2592). By enforcing `import type` for type-only imports, this commit improves code clarity and supports tooling optimization, ensuring imports are only bundled when necessary for runtime. Changes: - Bump TypeScript to 5.3.3 across the project. - Adjust import statements to utilize `import type` where applicable, promoting cleaner and more efficient code.
261 lines
11 KiB
TypeScript
261 lines
11 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { type BrowserCondition, TouchSupportExpectation } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserOs/BrowserCondition';
|
|
import { ConditionBasedOsDetector } from '@/infrastructure/RuntimeEnvironment/Browser/BrowserOs/ConditionBasedOsDetector';
|
|
import { getAbsentStringTestCases, itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
import { BrowserEnvironmentStub } from '@tests/unit/shared/Stubs/BrowserEnvironmentStub';
|
|
import { BrowserConditionStub } from '@tests/unit/shared/Stubs/BrowserConditionStub';
|
|
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
|
|
|
|
describe('ConditionBasedOsDetector', () => {
|
|
describe('constructor', () => {
|
|
describe('throws when given no conditions', () => {
|
|
itEachAbsentCollectionValue<BrowserCondition>((absentCollection) => {
|
|
// arrange
|
|
const expectedError = 'empty conditions';
|
|
const conditions = absentCollection;
|
|
// act
|
|
const act = () => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions(conditions)
|
|
.build();
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
}, { excludeUndefined: true, excludeNull: true });
|
|
});
|
|
it('throws if user agent part is missing', () => {
|
|
// arrange
|
|
const expectedError = 'Each condition must include at least one identifiable part of the user agent string.';
|
|
const invalidCondition = new BrowserConditionStub().withExistingPartsInSameUserAgent([]);
|
|
// act
|
|
const act = () => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([invalidCondition])
|
|
.build();
|
|
// assert
|
|
expect(act).to.throw(expectedError);
|
|
});
|
|
describe('validates touch support expectation range', () => {
|
|
// arrange
|
|
const validValue = TouchSupportExpectation.MustExist;
|
|
// act
|
|
const act = (touchSupport: TouchSupportExpectation) => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([new BrowserConditionStub().withTouchSupport(touchSupport)])
|
|
.build();
|
|
// assert
|
|
new EnumRangeTestRunner(act)
|
|
.testOutOfRangeThrows()
|
|
.testValidValueDoesNotThrow(validValue);
|
|
});
|
|
it('throws if duplicate parts exist in user agent', () => {
|
|
// arrange
|
|
const expectedError = 'Found duplicate entries in user agent parts: Windows. Each part should be unique.';
|
|
const invalidCondition = {
|
|
operatingSystem: OperatingSystem.Windows,
|
|
existingPartsInSameUserAgent: ['Windows', 'Windows'],
|
|
};
|
|
// act
|
|
const act = () => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([invalidCondition])
|
|
.build();
|
|
// assert
|
|
expect(act).toThrowError(expectedError);
|
|
});
|
|
it('throws if duplicate non-existing parts exist in user agent', () => {
|
|
// arrange
|
|
const expectedError = 'Found duplicate entries in user agent parts: Linux. Each part should be unique.';
|
|
const invalidCondition = {
|
|
operatingSystem: OperatingSystem.Linux,
|
|
existingPartsInSameUserAgent: ['Linux'],
|
|
notExistingPartsInUserAgent: ['Linux'],
|
|
};
|
|
// act
|
|
const act = () => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([invalidCondition])
|
|
.build();
|
|
// assert
|
|
expect(act).toThrowError(expectedError);
|
|
});
|
|
it('throws if duplicates found in any user agent parts', () => {
|
|
// arrange
|
|
const expectedError = 'Found duplicate entries in user agent parts: Android. Each part should be unique.';
|
|
const invalidCondition = {
|
|
operatingSystem: OperatingSystem.Android,
|
|
existingPartsInSameUserAgent: ['Android'],
|
|
notExistingPartsInUserAgent: ['iOS', 'Android'],
|
|
};
|
|
// act
|
|
const act = () => new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([invalidCondition])
|
|
.build();
|
|
// assert
|
|
expect(act).toThrowError(expectedError);
|
|
});
|
|
});
|
|
describe('detect', () => {
|
|
it('detects the correct OS when multiple conditions match', () => {
|
|
// arrange
|
|
const expectedOperatingSystem = OperatingSystem.Linux;
|
|
const testUserAgent = 'test-user-agent';
|
|
const expectedCondition = new BrowserConditionStub()
|
|
.withOperatingSystem(expectedOperatingSystem)
|
|
.withExistingPartsInSameUserAgent([testUserAgent]);
|
|
const conditions = [
|
|
expectedCondition,
|
|
new BrowserConditionStub()
|
|
.withExistingPartsInSameUserAgent(['unrelated user agent'])
|
|
.withOperatingSystem(OperatingSystem.Android),
|
|
new BrowserConditionStub()
|
|
.withNotExistingPartsInUserAgent([testUserAgent])
|
|
.withOperatingSystem(OperatingSystem.macOS),
|
|
];
|
|
const environment = new BrowserEnvironmentStub()
|
|
.withUserAgent(testUserAgent);
|
|
const detector = new ConditionBasedOsDetectorBuilder()
|
|
.withConditions(conditions)
|
|
.build();
|
|
// act
|
|
const actualOperatingSystem = detector.detect(environment);
|
|
// assert
|
|
expect(actualOperatingSystem).to.equal(expectedOperatingSystem);
|
|
});
|
|
|
|
describe('user agent checks', () => {
|
|
const testScenarios: ReadonlyArray<{
|
|
readonly description: string;
|
|
readonly buildEnvironment: (environment: BrowserEnvironmentStub) => BrowserEnvironmentStub;
|
|
readonly buildCondition: (condition: BrowserConditionStub) => BrowserConditionStub;
|
|
readonly detects: boolean;
|
|
}> = [
|
|
...getAbsentStringTestCases({ excludeUndefined: true, excludeNull: true })
|
|
.map((testCase) => ({
|
|
description: `does not detect when user agent is empty (${testCase.valueName})`,
|
|
buildEnvironment: (environment) => environment.withUserAgent(testCase.absentValue),
|
|
buildCondition: (condition) => condition,
|
|
detects: false,
|
|
})),
|
|
{
|
|
description: 'detects when user agent matches completely',
|
|
buildEnvironment: (environment) => environment.withUserAgent('test-user-agent'),
|
|
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['test-user-agent']),
|
|
detects: true,
|
|
},
|
|
{
|
|
description: 'detects when substring of user agent exists',
|
|
buildEnvironment: (environment) => environment.withUserAgent('test-user-agent'),
|
|
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['test']),
|
|
detects: true,
|
|
},
|
|
{
|
|
description: 'does not detect when no part of user agent exists',
|
|
buildEnvironment: (environment) => environment.withUserAgent('unrelated-user-agent'),
|
|
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['lorem-ipsum']),
|
|
detects: false,
|
|
},
|
|
{
|
|
description: 'detects when non-existing parts do not match',
|
|
buildEnvironment: (environment) => environment.withUserAgent('1-3'),
|
|
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['1']).withNotExistingPartsInUserAgent(['2']),
|
|
detects: true,
|
|
},
|
|
{
|
|
description: 'does not detect when non-existing and existing parts match',
|
|
buildEnvironment: (environment) => environment.withUserAgent('1-2'),
|
|
buildCondition: (condition) => condition.withExistingPartsInSameUserAgent(['1']).withNotExistingPartsInUserAgent(['2']),
|
|
detects: false,
|
|
},
|
|
];
|
|
testScenarios.forEach(({
|
|
description, buildEnvironment, buildCondition, detects,
|
|
}) => {
|
|
it(description, () => {
|
|
// arrange
|
|
const environment = buildEnvironment(new BrowserEnvironmentStub());
|
|
const condition = buildCondition(
|
|
new BrowserConditionStub().withOperatingSystem(OperatingSystem.Linux),
|
|
);
|
|
const detector = new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([condition])
|
|
.build();
|
|
// act
|
|
const actualOperatingSystem = detector.detect(environment);
|
|
// assert
|
|
expect(actualOperatingSystem !== undefined).to.equal(detects);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('touch support checks', () => {
|
|
const testScenarios: ReadonlyArray<{
|
|
readonly description: string;
|
|
readonly expectation: TouchSupportExpectation;
|
|
readonly isTouchSupportInEnvironment: boolean;
|
|
readonly detects: boolean;
|
|
}> = [
|
|
{
|
|
description: 'detects when touch support exists and is expected',
|
|
expectation: TouchSupportExpectation.MustExist,
|
|
isTouchSupportInEnvironment: true,
|
|
detects: true,
|
|
},
|
|
{
|
|
description: 'does not detect when touch support does not exists but is expected',
|
|
expectation: TouchSupportExpectation.MustExist,
|
|
isTouchSupportInEnvironment: false,
|
|
detects: false,
|
|
},
|
|
{
|
|
description: 'detects when touch support does not exist and is not expected',
|
|
expectation: TouchSupportExpectation.MustNotExist,
|
|
isTouchSupportInEnvironment: false,
|
|
detects: true,
|
|
},
|
|
{
|
|
description: 'does not detect when touch support exists but is not expected',
|
|
expectation: TouchSupportExpectation.MustNotExist,
|
|
isTouchSupportInEnvironment: true,
|
|
detects: false,
|
|
},
|
|
];
|
|
testScenarios.forEach(({
|
|
description, expectation, isTouchSupportInEnvironment, detects,
|
|
}) => {
|
|
it(description, () => {
|
|
// arrange
|
|
const userAgent = 'iPhone';
|
|
const environment = new BrowserEnvironmentStub()
|
|
.withUserAgent(userAgent)
|
|
.withIsTouchSupported(isTouchSupportInEnvironment);
|
|
const conditionWithTouchSupport = new BrowserConditionStub()
|
|
.withExistingPartsInSameUserAgent([userAgent])
|
|
.withTouchSupport(expectation);
|
|
const detector = new ConditionBasedOsDetectorBuilder()
|
|
.withConditions([conditionWithTouchSupport])
|
|
.build();
|
|
// act
|
|
const actualOperatingSystem = detector.detect(environment);
|
|
// assert
|
|
expect(actualOperatingSystem !== undefined)
|
|
.to.equal(detects);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
class ConditionBasedOsDetectorBuilder {
|
|
private conditions: readonly BrowserCondition[] = [{
|
|
operatingSystem: OperatingSystem.iOS,
|
|
existingPartsInSameUserAgent: ['iPhone'],
|
|
}];
|
|
|
|
public withConditions(conditions: readonly BrowserCondition[]): this {
|
|
this.conditions = conditions;
|
|
return this;
|
|
}
|
|
|
|
public build(): ConditionBasedOsDetector {
|
|
return new ConditionBasedOsDetector(
|
|
this.conditions,
|
|
);
|
|
}
|
|
}
|