Files
privacy.sexy/tests/unit/domain/Category.spec.ts
undergroundwires 4212c7b9e0 Improve context for errors thrown by compiler
This commit introduces a custom error object to provide additional
context for errors throwing during parsing and compiling operations,
improving troubleshooting.

By integrating error context handling, the error messages become more
informative and user-friendly, providing sequence of trace with context
to aid in troubleshooting.

Changes include:

- Introduce custom error object that extends errors with contextual
  information. This replaces previous usages of `AggregateError` which
  is not displayed well by browsers when logged.
- Improve parsing functions to encapsulate error context with more
  details.
- Increase unit test coverage and refactor the related code to be more
  testable.
2024-05-25 13:55:30 +02:00

217 lines
6.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Category } from '@/domain/Category';
import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
import type { ICategory, IScript } from '@/domain/ICategory';
describe('Category', () => {
describe('ctor', () => {
describe('throws error if name is absent', () => {
itEachAbsentStringValue((absentValue) => {
// arrange
const expectedError = 'missing name';
const name = absentValue;
// act
const construct = () => new CategoryBuilder()
.withName(name)
.build();
// assert
expect(construct).to.throw(expectedError);
}, { excludeNull: true, excludeUndefined: true });
});
it('throws error if no children are present', () => {
// arrange
const expectedError = 'A category must have at least one sub-category or script';
const scriptChildren: readonly IScript[] = [];
const categoryChildren: readonly ICategory[] = [];
// act
const construct = () => new CategoryBuilder()
.withSubcategories(categoryChildren)
.withScripts(scriptChildren)
.build();
// assert
expect(construct).to.throw(expectedError);
});
});
describe('getAllScriptsRecursively', () => {
it('retrieves direct child scripts', () => {
// arrange
const expectedScripts = [new ScriptStub('1'), new ScriptStub('2')];
const sut = new CategoryBuilder()
.withScripts(expectedScripts)
.build();
// act
const actual = sut.getAllScriptsRecursively();
// assert
expect(actual).to.have.deep.members(expectedScripts);
});
it('retrieves scripts from direct child categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const sut = new CategoryBuilder()
.withScripts([])
.withSubcategories(categories)
.build();
// act
const actualIds = sut
.getAllScriptsRecursively()
.map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('retrieves scripts from both direct children and child categories', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
const categories = [
new CategoryStub(31).withScriptIds('1', '2'),
new CategoryStub(32).withScriptIds('3', '4'),
];
const scripts = [new ScriptStub('5'), new ScriptStub('6')];
const sut = new CategoryBuilder()
.withSubcategories(categories)
.withScripts(scripts)
.build();
// act
const actualIds = sut
.getAllScriptsRecursively()
.map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
it('retrieves scripts from nested categories recursively', () => {
// arrange
const expectedScriptIds = ['1', '2', '3', '4', '5', '6'];
const categories = [
new CategoryStub(31)
.withScriptIds('1', '2')
.withCategory(
new CategoryStub(32)
.withScriptIds('3', '4'),
),
new CategoryStub(33)
.withCategories(
new CategoryStub(34)
.withScriptIds('5')
.withCategory(
new CategoryStub(35)
.withCategory(
new CategoryStub(35).withScriptIds('6'),
),
),
),
];
// assert
const sut = new CategoryBuilder()
.withScripts([])
.withSubcategories(categories)
.build();
// act
const actualIds = sut
.getAllScriptsRecursively()
.map((s) => s.id);
// assert
expect(actualIds).to.have.deep.members(expectedScriptIds);
});
});
describe('includes', () => {
it('returns false for scripts not included', () => {
// assert
const expectedResult = false;
const script = new ScriptStub('3');
const childCategory = new CategoryStub(33)
.withScriptIds('1', '2');
const sut = new CategoryBuilder()
.withSubcategories([childCategory])
.build();
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(expectedResult);
});
it('returns true for scripts directly included', () => {
// assert
const expectedResult = true;
const script = new ScriptStub('3');
const childCategory = new CategoryStub(33)
.withScript(script)
.withScriptIds('non-related');
const sut = new CategoryBuilder()
.withSubcategories([childCategory])
.build();
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(expectedResult);
});
it('returns true for scripts included in nested categories', () => {
// assert
const expectedResult = true;
const script = new ScriptStub('3');
const childCategory = new CategoryStub(22)
.withScriptIds('non-related')
.withCategory(new CategoryStub(33).withScript(script));
const sut = new CategoryBuilder()
.withSubcategories([childCategory])
.build();
// act
const actual = sut.includes(script);
// assert
expect(actual).to.equal(expectedResult);
});
});
});
class CategoryBuilder {
private id = 3264;
private name = 'test-script';
private docs: ReadonlyArray<string> = [];
private subcategories: ReadonlyArray<ICategory> = [];
private scripts: ReadonlyArray<IScript> = [
new ScriptStub(`[${CategoryBuilder.name}] script`),
];
public withId(id: number): this {
this.id = id;
return this;
}
public withName(name: string): this {
this.name = name;
return this;
}
public withDocs(docs: ReadonlyArray<string>): this {
this.docs = docs;
return this;
}
public withScripts(scripts: ReadonlyArray<IScript>): this {
this.scripts = scripts;
return this;
}
public withSubcategories(subcategories: ReadonlyArray<ICategory>): this {
this.subcategories = subcategories;
return this;
}
public build(): Category {
return new Category({
id: this.id,
name: this.name,
docs: this.docs,
subcategories: this.subcategories,
scripts: this.scripts,
});
}
}