diff --git a/docs/tests.md b/docs/tests.md index 0cd4e7d6..43ed71d9 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -13,9 +13,29 @@ - Tests each component in isolation. - Defined in [`./tests/unit`](./../tests/unit). -- They follow same folder structure as [`./src`](./../src). -### Naming +### Unit tests structure + +- [`./src/`](./../src/) + - Includes code that will be tested tested. +- [`./tests/unit/`](./../tests/unit/) + - Includes test code. + - Tests follow same folder structure as [`./src/`](./../src). + - E.g. if system under test lies in [`./src/application/ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts) then its tests would be in test would be at [`./tests/unit/application/ApplicationFactory.spec.ts`](./../tests/unit/application/ApplicationFactory.spec.ts). + - [`shared/`](./../tests/unit/shared/) + - Includes common functionality that's shared across unit tests. + - [`Assertions/`](./../tests/unit/shared/Assertions): + - Common assertions that extend [Chai Assertion Library](https://www.chaijs.com/). + - Asserting functions should start with `expect` prefix. + - [`TestCases/`](./../tests/unit/shared/TestCases/) + - Shared test cases. + - Test runner functions that uses `it()` from Mocha test [Mocha test framework](https://mochajs.org/) should be prefixed with `it.` + - E.g. `itEachAbsentCollectionValue()`. + - [`stubs/`](./../tests/unit/stubs) + - Includes stubs to be able to test classes in isolation. + - They implement dummy behavior to be functional with optionally spying or mocking functions. + +### Unit tests naming - Each test suite first describe the system under test. - E.g. tests for class `Application` is categorized under `Application`. @@ -35,11 +55,6 @@ - Should elicit some sort of response. - Starts with comment line `// assert`. -### Stubs - -- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs). -- They implement dummy behavior to be functional. - ## Integration tests - Tests functionality of a component in combination with others (not isolated). diff --git a/src/application/ApplicationFactory.ts b/src/application/ApplicationFactory.ts index 0ab31304..6e80451e 100644 --- a/src/application/ApplicationFactory.ts +++ b/src/application/ApplicationFactory.ts @@ -13,7 +13,7 @@ export class ApplicationFactory implements IApplicationFactory { protected constructor(costlyGetter: ApplicationGetterType) { if (!costlyGetter) { - throw new Error('undefined getter'); + throw new Error('missing getter'); } this.getter = new AsyncLazy(() => Promise.resolve(costlyGetter())); } diff --git a/src/application/Common/Array.ts b/src/application/Common/Array.ts index c3444578..a3f95dfd 100644 --- a/src/application/Common/Array.ts +++ b/src/application/Common/Array.ts @@ -1,7 +1,7 @@ // Compares to Array objects for equality, ignoring order export function scrambledEqual(array1: readonly T[], array2: readonly T[]) { - if (!array1) { throw new Error('undefined first array'); } - if (!array2) { throw new Error('undefined second array'); } + if (!array1) { throw new Error('missing first array'); } + if (!array2) { throw new Error('missing second array'); } const sortedArray1 = sort(array1); const sortedArray2 = sort(array2); return sequenceEqual(sortedArray1, sortedArray2); @@ -12,8 +12,8 @@ export function scrambledEqual(array1: readonly T[], array2: readonly T[]) { // Compares to Array objects for equality in same order export function sequenceEqual(array1: readonly T[], array2: readonly T[]) { - if (!array1) { throw new Error('undefined first array'); } - if (!array2) { throw new Error('undefined second array'); } + if (!array1) { throw new Error('missing first array'); } + if (!array2) { throw new Error('missing second array'); } if (array1.length !== array2.length) { return false; } diff --git a/src/application/Common/Enum.ts b/src/application/Common/Enum.ts index ac2bba57..624a52ab 100644 --- a/src/application/Common/Enum.ts +++ b/src/application/Common/Enum.ts @@ -21,7 +21,7 @@ function parseEnumValue( enumVariable: EnumVariable, ): TEnumValue { if (!value) { - throw new Error(`undefined ${enumName}`); + throw new Error(`missing ${enumName}`); } if (typeof value !== 'string') { throw new Error(`unexpected type of ${enumName}: "${typeof value}"`); @@ -54,8 +54,8 @@ export function assertInRange( value: TEnumValue, enumVariable: EnumVariable, ) { - if (value === undefined) { - throw new Error('undefined enum value'); + if (value === undefined || value === null) { + throw new Error('absent enum value'); } if (!(value in enumVariable)) { throw new RangeError(`enum value "${value}" is out of range`); diff --git a/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts b/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts index fb71e0d6..6ae46fdf 100644 --- a/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts +++ b/src/application/Common/ScriptingLanguage/ScriptingLanguageFactory.ts @@ -20,7 +20,7 @@ export abstract class ScriptingLanguageFactory implements IScriptingLanguageF protected registerGetter(language: ScriptingLanguage, getter: Getter) { assertInRange(language, ScriptingLanguage); if (!getter) { - throw new Error('undefined getter'); + throw new Error('missing getter'); } if (this.getters.has(language)) { throw new Error(`${ScriptingLanguage[language]} is already registered`); diff --git a/src/application/Context/ApplicationContext.ts b/src/application/Context/ApplicationContext.ts index 68557136..2ed02b5f 100644 --- a/src/application/Context/ApplicationContext.ts +++ b/src/application/Context/ApplicationContext.ts @@ -27,12 +27,12 @@ export class ApplicationContext implements IApplicationContext { initialContext: OperatingSystem, ) { validateApp(app); - assertInRange(initialContext, OperatingSystem); this.states = initializeStates(app); this.changeContext(initialContext); } public changeContext(os: OperatingSystem): void { + assertInRange(os, OperatingSystem); if (this.currentOs === os) { return; } @@ -51,7 +51,7 @@ export class ApplicationContext implements IApplicationContext { function validateApp(app: IApplication) { if (!app) { - throw new Error('undefined app'); + throw new Error('missing app'); } } diff --git a/src/application/Context/ApplicationContextFactory.ts b/src/application/Context/ApplicationContextFactory.ts index 2fead72e..628b1b1a 100644 --- a/src/application/Context/ApplicationContextFactory.ts +++ b/src/application/Context/ApplicationContextFactory.ts @@ -11,8 +11,8 @@ export async function buildContext( factory: IApplicationFactory = ApplicationFactory.Current, environment = Environment.CurrentEnvironment, ): Promise { - if (!factory) { throw new Error('undefined factory'); } - if (!environment) { throw new Error('undefined environment'); } + if (!factory) { throw new Error('missing factory'); } + if (!environment) { throw new Error('missing environment'); } const app = await factory.getApp(); const os = getInitialOs(app, environment); return new ApplicationContext(app, os); diff --git a/src/application/Context/State/Code/ApplicationCode.ts b/src/application/Context/State/Code/ApplicationCode.ts index 3b517ef1..165d809b 100644 --- a/src/application/Context/State/Code/ApplicationCode.ts +++ b/src/application/Context/State/Code/ApplicationCode.ts @@ -21,9 +21,9 @@ export class ApplicationCode implements IApplicationCode { private readonly scriptingDefinition: IScriptingDefinition, private readonly generator: IUserScriptGenerator = new UserScriptGenerator(), ) { - if (!userSelection) { throw new Error('userSelection is null or undefined'); } - if (!scriptingDefinition) { throw new Error('scriptingDefinition is null or undefined'); } - if (!generator) { throw new Error('generator is null or undefined'); } + if (!userSelection) { throw new Error('missing userSelection'); } + if (!scriptingDefinition) { throw new Error('missing scriptingDefinition'); } + if (!generator) { throw new Error('missing generator'); } this.setCode(userSelection.selectedScripts); userSelection.changed.on((scripts) => { this.setCode(scripts); diff --git a/src/application/Context/State/Code/Generation/UserScriptGenerator.ts b/src/application/Context/State/Code/Generation/UserScriptGenerator.ts index 5a37ab11..7743942b 100644 --- a/src/application/Context/State/Code/Generation/UserScriptGenerator.ts +++ b/src/application/Context/State/Code/Generation/UserScriptGenerator.ts @@ -17,8 +17,8 @@ export class UserScriptGenerator implements IUserScriptGenerator { selectedScripts: ReadonlyArray, scriptingDefinition: IScriptingDefinition, ): IUserScript { - if (!selectedScripts) { throw new Error('undefined scripts'); } - if (!scriptingDefinition) { throw new Error('undefined definition'); } + if (!selectedScripts) { throw new Error('missing scripts'); } + if (!scriptingDefinition) { throw new Error('missing definition'); } if (!selectedScripts.length) { return { code: '', scriptPositions: new Map() }; } diff --git a/src/application/Environment/BrowserOs/DetectorBuilder.ts b/src/application/Environment/BrowserOs/DetectorBuilder.ts index ea7e0a37..9e8382ca 100644 --- a/src/application/Environment/BrowserOs/DetectorBuilder.ts +++ b/src/application/Environment/BrowserOs/DetectorBuilder.ts @@ -27,7 +27,7 @@ export class DetectorBuilder { private detect(userAgent: string): OperatingSystem { if (!userAgent) { - throw new Error('User agent is null or undefined'); + throw new Error('missing userAgent'); } if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) { return undefined; diff --git a/src/application/Parser/ApplicationParser.ts b/src/application/Parser/ApplicationParser.ts index a3502906..153fe0fc 100644 --- a/src/application/Parser/ApplicationParser.ts +++ b/src/application/Parser/ApplicationParser.ts @@ -32,10 +32,10 @@ const PreParsedCollections: readonly CollectionData [] = [ ]; function validateCollectionsData(collections: readonly CollectionData[]) { - if (!collections.length) { - throw new Error('no collection provided'); + if (!collections || !collections.length) { + throw new Error('missing collections'); } if (collections.some((collection) => !collection)) { - throw new Error('undefined collection provided'); + throw new Error('missing collection provided'); } } diff --git a/src/application/Parser/CategoryCollectionParser.ts b/src/application/Parser/CategoryCollectionParser.ts index e9c27ce1..29983988 100644 --- a/src/application/Parser/CategoryCollectionParser.ts +++ b/src/application/Parser/CategoryCollectionParser.ts @@ -29,7 +29,7 @@ export function parseCategoryCollection( function validate(content: CollectionData): void { if (!content) { - throw new Error('content is null or undefined'); + throw new Error('missing content'); } if (!content.actions || content.actions.length <= 0) { throw new Error('content does not define any action'); diff --git a/src/application/Parser/CategoryParser.ts b/src/application/Parser/CategoryParser.ts index eeae57b0..c537f679 100644 --- a/src/application/Parser/CategoryParser.ts +++ b/src/application/Parser/CategoryParser.ts @@ -13,7 +13,7 @@ export function parseCategory( category: CategoryData, context: ICategoryCollectionParseContext, ): Category { - if (!context) { throw new Error('undefined context'); } + if (!context) { throw new Error('missing context'); } ensureValid(category); const children: ICategoryChildren = { subCategories: new Array(), @@ -33,7 +33,7 @@ export function parseCategory( function ensureValid(category: CategoryData) { if (!category) { - throw Error('category is null or undefined'); + throw Error('missing category'); } if (!category.children || category.children.length === 0) { throw Error(`category has no children: "${category.category}"`); diff --git a/src/application/Parser/DocumentationParser.ts b/src/application/Parser/DocumentationParser.ts index e90557f1..d9b21d74 100644 --- a/src/application/Parser/DocumentationParser.ts +++ b/src/application/Parser/DocumentationParser.ts @@ -2,7 +2,7 @@ import { DocumentableData, DocumentationUrlsData } from 'js-yaml-loader!@/*'; export function parseDocUrls(documentable: DocumentableData): ReadonlyArray { if (!documentable) { - throw new Error('documentable is null or undefined'); + throw new Error('missing documentable'); } const { docs } = documentable; if (!docs || !docs.length) { diff --git a/src/application/Parser/Script/CategoryCollectionParseContext.ts b/src/application/Parser/Script/CategoryCollectionParseContext.ts index 32c2fbb9..6a376d73 100644 --- a/src/application/Parser/Script/CategoryCollectionParseContext.ts +++ b/src/application/Parser/Script/CategoryCollectionParseContext.ts @@ -17,7 +17,7 @@ export class CategoryCollectionParseContext implements ICategoryCollectionParseC scripting: IScriptingDefinition, syntaxFactory: ISyntaxFactory = new SyntaxFactory(), ) { - if (!scripting) { throw new Error('undefined scripting'); } + if (!scripting) { throw new Error('missing scripting'); } this.syntax = syntaxFactory.create(scripting.language); this.compiler = new ScriptCompiler(functionsData, this.syntax); } diff --git a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts index 9142bace..f44fa287 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Expression/Expression.ts @@ -8,23 +8,25 @@ import { ExpressionEvaluationContext, IExpressionEvaluationContext } from './Exp export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string; export class Expression implements IExpression { + public readonly parameters: IReadOnlyFunctionParameterCollection; + constructor( public readonly position: ExpressionPosition, public readonly evaluator: ExpressionEvaluator, - public readonly parameters - : IReadOnlyFunctionParameterCollection = new FunctionParameterCollection(), + parameters?: IReadOnlyFunctionParameterCollection, ) { if (!position) { - throw new Error('undefined position'); + throw new Error('missing position'); } if (!evaluator) { - throw new Error('undefined evaluator'); + throw new Error('missing evaluator'); } + this.parameters = parameters ?? new FunctionParameterCollection(); } public evaluate(context: IExpressionEvaluationContext): string { if (!context) { - throw new Error('undefined context'); + throw new Error('missing context'); } validateThatAllRequiredParametersAreSatisfied(this.parameters, context.args); const args = filterUnusedArguments(this.parameters, context.args); diff --git a/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.ts b/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.ts index 18fddc8c..74362320 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.ts @@ -13,7 +13,7 @@ export class ExpressionEvaluationContext implements IExpressionEvaluationContext public readonly pipelineCompiler: IPipelineCompiler = new PipelineCompiler(), ) { if (!args) { - throw new Error('undefined args, send empty collection instead'); + throw new Error('missing args, send empty collection instead.'); } } } diff --git a/src/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.ts b/src/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.ts index a6a726e6..9177a77e 100644 --- a/src/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.ts +++ b/src/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.ts @@ -15,7 +15,7 @@ export class ExpressionsCompiler implements IExpressionsCompiler { args: IReadOnlyFunctionCallArgumentCollection, ): string { if (!args) { - throw new Error('undefined args, send empty collection instead'); + throw new Error('missing args, send empty collection instead.'); } if (!code) { return code; diff --git a/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts b/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts index 6e43abe5..aa75c480 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts @@ -10,8 +10,11 @@ const Parsers = [ export class CompositeExpressionParser implements IExpressionParser { public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) { + if (!leafs) { + throw new Error('missing leafs'); + } if (leafs.some((leaf) => !leaf)) { - throw new Error('undefined leaf'); + throw new Error('missing leaf'); } } diff --git a/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts b/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts index 89ec583a..7bc53d0c 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts @@ -16,7 +16,7 @@ export abstract class RegexParser implements IExpressionParser { private* findRegexExpressions(code: string): Iterable { if (!code) { - throw new Error('undefined code'); + throw new Error('missing code'); } const matches = code.matchAll(this.regex); for (const match of matches) { diff --git a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.ts b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.ts index 56a1f71f..32562be9 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.ts @@ -4,7 +4,10 @@ export class EscapeDoubleQuotes implements IPipe { public readonly name: string = 'escapeDoubleQuotes'; public apply(raw: string): string { - return raw?.replaceAll('"', '"^""'); + if (!raw) { + return raw; + } + return raw.replaceAll('"', '"^""'); /* eslint-disable max-len */ /* "^"" is the most robust and stable choice. diff --git a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.ts b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.ts index 5bcede82..5b47a9db 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.ts @@ -15,8 +15,11 @@ export class PipeFactory implements IPipeFactory { private readonly pipes = new Map(); constructor(pipes: readonly IPipe[] = RegisteredPipes) { + if (!pipes) { + throw new Error('missing pipes'); + } if (pipes.some((pipe) => !pipe)) { - throw new Error('undefined pipe in list'); + throw new Error('missing pipe in list'); } for (const pipe of pipes) { this.registerPipe(pipe); diff --git a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.ts b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.ts index 344ed1fe..421e04d6 100644 --- a/src/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.ts +++ b/src/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.ts @@ -23,8 +23,8 @@ function extractPipeNames(pipeline: string): string[] { } function ensureValidArguments(value: string, pipeline: string) { - if (!value) { throw new Error('undefined value'); } - if (!pipeline) { throw new Error('undefined pipeline'); } + if (!value) { throw new Error('missing value'); } + if (!pipeline) { throw new Error('missing pipeline'); } if (!pipeline.trimStart().startsWith('|')) { throw new Error('pipeline does not start with pipe'); } diff --git a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts index e088cb7a..5b37093b 100644 --- a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts +++ b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts @@ -8,7 +8,7 @@ export class FunctionCallArgument implements IFunctionCallArgument { ) { ensureValidParameterName(parameterName); if (!argumentValue) { - throw new Error(`undefined argument value for "${parameterName}"`); + throw new Error(`missing argument value for "${parameterName}"`); } } } diff --git a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts index c26ac023..9775b6b5 100644 --- a/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts +++ b/src/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts @@ -6,7 +6,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl public addArgument(argument: IFunctionCallArgument): void { if (!argument) { - throw new Error('undefined argument'); + throw new Error('missing argument'); } if (this.hasArgument(argument.parameterName)) { throw new Error(`argument value for parameter ${argument.parameterName} is already provided`); @@ -20,14 +20,14 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl public hasArgument(parameterName: string): boolean { if (!parameterName) { - throw new Error('undefined parameter name'); + throw new Error('missing parameter name'); } return this.arguments.has(parameterName); } public getArgument(parameterName: string): IFunctionCallArgument { if (!parameterName) { - throw new Error('undefined parameter name'); + throw new Error('missing parameter name'); } const arg = this.arguments.get(parameterName); if (!arg) { diff --git a/src/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.ts b/src/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.ts index db8e5ac5..5db2fb88 100644 --- a/src/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.ts +++ b/src/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.ts @@ -22,9 +22,9 @@ export class FunctionCallCompiler implements IFunctionCallCompiler { calls: IFunctionCall[], functions: ISharedFunctionCollection, ): ICompiledCode { - if (!functions) { throw new Error('undefined functions'); } - if (!calls) { throw new Error('undefined calls'); } - if (calls.some((f) => !f)) { throw new Error('undefined function call'); } + if (!functions) { throw new Error('missing functions'); } + if (!calls) { throw new Error('missing calls'); } + if (calls.some((f) => !f)) { throw new Error('missing function call'); } const context: ICompilationContext = { allFunctions: functions, callSequence: calls, diff --git a/src/application/Parser/Script/Compiler/Function/Call/FunctionCall.ts b/src/application/Parser/Script/Compiler/Function/Call/FunctionCall.ts index 6696b418..9554cb81 100644 --- a/src/application/Parser/Script/Compiler/Function/Call/FunctionCall.ts +++ b/src/application/Parser/Script/Compiler/Function/Call/FunctionCall.ts @@ -7,10 +7,10 @@ export class FunctionCall implements IFunctionCall { public readonly args: IReadOnlyFunctionCallArgumentCollection, ) { if (!functionName) { - throw new Error('empty function name in function call'); + throw new Error('missing function name in function call'); } if (!args) { - throw new Error('undefined args'); + throw new Error('missing args'); } } } diff --git a/src/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.ts b/src/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.ts index ccb6f776..6bd19da7 100644 --- a/src/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.ts +++ b/src/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.ts @@ -6,7 +6,7 @@ import { FunctionCall } from './FunctionCall'; export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] { if (calls === undefined) { - throw new Error('undefined call data'); + throw new Error('missing call data'); } const sequence = getCallSequence(calls); return sequence.map((call) => parseFunctionCall(call)); @@ -24,7 +24,7 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] { function parseFunctionCall(call: FunctionCallData): IFunctionCall { if (!call) { - throw new Error('undefined function call'); + throw new Error('missing call data'); } const callArgs = parseArgs(call.parameters); return new FunctionCall(call.function, callArgs); diff --git a/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts b/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts index fe439a28..f680b9e4 100644 --- a/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts +++ b/src/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts @@ -19,7 +19,7 @@ export class FunctionParameterCollection implements IFunctionParameterCollection private ensureValidParameter(parameter: IFunctionParameter) { if (!parameter) { - throw new Error('undefined parameter'); + throw new Error('missing parameter'); } if (this.includesName(parameter.name)) { throw new Error(`duplicate parameter name: "${parameter.name}"`); diff --git a/src/application/Parser/Script/Compiler/Function/Shared/ParameterNameValidator.ts b/src/application/Parser/Script/Compiler/Function/Shared/ParameterNameValidator.ts index d8b4183e..a5490bb3 100644 --- a/src/application/Parser/Script/Compiler/Function/Shared/ParameterNameValidator.ts +++ b/src/application/Parser/Script/Compiler/Function/Shared/ParameterNameValidator.ts @@ -1,6 +1,6 @@ export function ensureValidParameterName(parameterName: string) { if (!parameterName) { - throw new Error('undefined parameter name'); + throw new Error('missing parameter name'); } if (!parameterName.match(/^[0-9a-zA-Z]+$/)) { throw new Error(`parameter name must be alphanumeric but it was "${parameterName}"`); diff --git a/src/application/Parser/Script/Compiler/Function/SharedFunction.ts b/src/application/Parser/Script/Compiler/Function/SharedFunction.ts index f2fd6d5e..77335476 100644 --- a/src/application/Parser/Script/Compiler/Function/SharedFunction.ts +++ b/src/application/Parser/Script/Compiler/Function/SharedFunction.ts @@ -9,11 +9,8 @@ export function createCallerFunction( parameters: IReadOnlyFunctionParameterCollection, callSequence: readonly IFunctionCall[], ): ISharedFunction { - if (!callSequence) { - throw new Error(`undefined call sequence in function "${name}"`); - } - if (!callSequence.length) { - throw new Error(`empty call sequence in function "${name}"`); + if (!callSequence || !callSequence.length) { + throw new Error(`missing call sequence in function "${name}"`); } return new SharedFunction(name, parameters, callSequence, FunctionBodyType.Calls); } @@ -43,8 +40,8 @@ class SharedFunction implements ISharedFunction { content: IFunctionCode | readonly IFunctionCall[], bodyType: FunctionBodyType, ) { - if (!name) { throw new Error('undefined function name'); } - if (!parameters) { throw new Error('undefined parameters'); } + if (!name) { throw new Error('missing function name'); } + if (!parameters) { throw new Error('missing parameters'); } this.body = { type: bodyType, code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined, diff --git a/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts b/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts index 9de1da13..a8679ea1 100644 --- a/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts +++ b/src/application/Parser/Script/Compiler/Function/SharedFunctionCollection.ts @@ -5,7 +5,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection { private readonly functionsByName = new Map(); public addFunction(func: ISharedFunction): void { - if (!func) { throw new Error('undefined function'); } + if (!func) { throw new Error('missing function'); } if (this.has(func.name)) { throw new Error(`function with name ${func.name} already exists`); } @@ -13,7 +13,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection { } public getFunctionByName(name: string): ISharedFunction { - if (!name) { throw Error('undefined function name'); } + if (!name) { throw Error('missing function name'); } const func = this.functionsByName.get(name); if (!func) { throw new Error(`called function is not defined "${name}"`); diff --git a/src/application/Parser/Script/Compiler/ScriptCompiler.ts b/src/application/Parser/Script/Compiler/ScriptCompiler.ts index efd25142..ae4f6a01 100644 --- a/src/application/Parser/Script/Compiler/ScriptCompiler.ts +++ b/src/application/Parser/Script/Compiler/ScriptCompiler.ts @@ -18,12 +18,12 @@ export class ScriptCompiler implements IScriptCompiler { private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance, sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance, ) { - if (!syntax) { throw new Error('undefined syntax'); } + if (!syntax) { throw new Error('missing syntax'); } this.functions = sharedFunctionsParser.parseFunctions(functions); } public canCompile(script: ScriptData): boolean { - if (!script) { throw new Error('undefined script'); } + if (!script) { throw new Error('missing script'); } if (!script.call) { return false; } @@ -31,7 +31,7 @@ export class ScriptCompiler implements IScriptCompiler { } public compile(script: ScriptData): IScriptCode { - if (!script) { throw new Error('undefined script'); } + if (!script) { throw new Error('missing script'); } try { const calls = parseFunctionCalls(script.call); const compiledCode = this.callCompiler.compileCall(calls, this.functions); diff --git a/src/application/Parser/Script/ScriptParser.ts b/src/application/Parser/Script/ScriptParser.ts index aafafcf1..b06a4d84 100644 --- a/src/application/Parser/Script/ScriptParser.ts +++ b/src/application/Parser/Script/ScriptParser.ts @@ -13,7 +13,7 @@ export function parseScript( levelParser = createEnumParser(RecommendationLevel), ): Script { validateScript(data); - if (!context) { throw new Error('undefined context'); } + if (!context) { throw new Error('missing context'); } const script = new Script( /* name: */ data.name, /* code: */ parseCode(data, context), @@ -51,7 +51,7 @@ function ensureNotBothCallAndCode(script: ScriptData) { function validateScript(script: ScriptData) { if (!script) { - throw new Error('undefined script'); + throw new Error('missing script'); } if (!script.code && !script.call) { throw new Error('must define either "call" or "code"'); diff --git a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts index 3df93cb2..46ec5721 100644 --- a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts +++ b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts @@ -16,8 +16,8 @@ export class CodeSubstituter implements ICodeSubstituter { } public substitute(code: string, info: IProjectInformation): string { - if (!code) { throw new Error('undefined code'); } - if (!info) { throw new Error('undefined info'); } + if (!code) { throw new Error('missing code'); } + if (!info) { throw new Error('missing info'); } const args = new FunctionCallArgumentCollection(); const substitute = (name: string, value: string) => args .addArgument(new FunctionCallArgument(name, value)); diff --git a/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts b/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts index 0b4edc57..77e037b0 100644 --- a/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts +++ b/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts @@ -18,8 +18,8 @@ export class ScriptingDefinitionParser { definition: ScriptingDefinitionData, info: IProjectInformation, ): IScriptingDefinition { - if (!info) { throw new Error('undefined info'); } - if (!definition) { throw new Error('undefined definition'); } + if (!info) { throw new Error('missing info'); } + if (!definition) { throw new Error('missing definition'); } const language = this.languageParser.parseEnum(definition.language, 'language'); const startCode = this.codeSubstituter.substitute(definition.startCode, info); const endCode = this.codeSubstituter.substitute(definition.endCode, info); diff --git a/src/domain/Application.ts b/src/domain/Application.ts index 2ed927c9..633db872 100644 --- a/src/domain/Application.ts +++ b/src/domain/Application.ts @@ -23,19 +23,16 @@ export class Application implements IApplication { function validateInformation(info: IProjectInformation) { if (!info) { - throw new Error('undefined project information'); + throw new Error('missing project information'); } } function validateCollections(collections: readonly ICategoryCollection[]) { - if (!collections) { - throw new Error('undefined collections'); - } - if (collections.length === 0) { - throw new Error('no collection in the list'); + if (!collections || !collections.length) { + throw new Error('missing collections'); } if (collections.filter((c) => !c).length > 0) { - throw new Error('undefined collection in the list'); + throw new Error('missing collection in the list'); } const osList = collections.map((c) => c.os); const duplicates = getDuplicates(osList); diff --git a/src/domain/Category.ts b/src/domain/Category.ts index 14d97718..d094bcc4 100644 --- a/src/domain/Category.ts +++ b/src/domain/Category.ts @@ -37,7 +37,7 @@ function parseScriptsRecursively(category: ICategory): ReadonlyArray { function validateCategory(category: ICategory) { if (!category.name) { - throw new Error('undefined or empty name'); + throw new Error('missing name'); } if ( (!category.subCategories || category.subCategories.length === 0) diff --git a/src/domain/CategoryCollection.ts b/src/domain/CategoryCollection.ts index 85400349..0a627dd2 100644 --- a/src/domain/CategoryCollection.ts +++ b/src/domain/CategoryCollection.ts @@ -20,7 +20,7 @@ export class CategoryCollection implements ICategoryCollection { public readonly scripting: IScriptingDefinition, ) { if (!scripting) { - throw new Error('undefined scripting definition'); + throw new Error('missing scripting definition'); } this.queryable = makeQueryable(actions); assertInRange(os, OperatingSystem); @@ -34,12 +34,7 @@ export class CategoryCollection implements ICategoryCollection { } public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] { - if (level === undefined) { - throw new Error('undefined level'); - } - if (!(level in RecommendationLevel)) { - throw new Error(`invalid level: ${level}`); - } + assertInRange(level, RecommendationLevel); return this.queryable.scriptsByLevel.get(level); } diff --git a/src/domain/Script.ts b/src/domain/Script.ts index 12344639..67ccd1b1 100644 --- a/src/domain/Script.ts +++ b/src/domain/Script.ts @@ -12,7 +12,7 @@ export class Script extends BaseEntity implements IScript { ) { super(name); if (!code) { - throw new Error(`undefined code (script: ${name})`); + throw new Error(`missing code (script: ${name})`); } validateLevel(level); } diff --git a/src/domain/ScriptCode.ts b/src/domain/ScriptCode.ts index 84ddec30..b2e282cc 100644 --- a/src/domain/ScriptCode.ts +++ b/src/domain/ScriptCode.ts @@ -6,7 +6,7 @@ export class ScriptCode implements IScriptCode { public readonly revert: string, syntax: ILanguageSyntax, ) { - if (!syntax) { throw new Error('undefined syntax'); } + if (!syntax) { throw new Error('missing syntax'); } validateCode(execute, syntax); validateRevertCode(revert, execute, syntax); } @@ -33,7 +33,7 @@ function validateRevertCode(revertCode: string, execute: string, syntax: ILangua function validateCode(code: string, syntax: ILanguageSyntax): void { if (!code || code.length === 0) { - throw new Error('code is empty or undefined'); + throw new Error('missing code'); } ensureNoEmptyLines(code); ensureCodeHasUniqueLines(code, syntax); diff --git a/src/domain/ScriptingDefinition.ts b/src/domain/ScriptingDefinition.ts index 2251068f..fe425c25 100644 --- a/src/domain/ScriptingDefinition.ts +++ b/src/domain/ScriptingDefinition.ts @@ -28,6 +28,6 @@ function findExtension(language: ScriptingLanguage): string { function validateCode(code: string, name: string) { if (!code) { - throw new Error(`undefined ${name}`); + throw new Error(`missing ${name}`); } } diff --git a/src/infrastructure/CodeRunner.ts b/src/infrastructure/CodeRunner.ts index 5af3637b..cd16fd53 100644 --- a/src/infrastructure/CodeRunner.ts +++ b/src/infrastructure/CodeRunner.ts @@ -34,7 +34,7 @@ function getExecuteCommand(scriptPath: string, environment: Environment): string case OperatingSystem.Windows: return scriptPath; default: - throw Error('undefined os'); + throw Error(`unsupported os: ${OperatingSystem[environment.os]}`); } } diff --git a/src/infrastructure/Repository/InMemoryRepository.ts b/src/infrastructure/Repository/InMemoryRepository.ts index 80b7555a..1096e9d2 100644 --- a/src/infrastructure/Repository/InMemoryRepository.ts +++ b/src/infrastructure/Repository/InMemoryRepository.ts @@ -27,7 +27,7 @@ implements IRepository { public addItem(item: TEntity): void { if (!item) { - throw new Error('item is null or undefined'); + throw new Error('missing item'); } if (this.exists(item.id)) { throw new Error(`Cannot add (id: ${item.id}) as it is already exists`); @@ -37,7 +37,7 @@ implements IRepository { public addOrUpdateItem(item: TEntity): void { if (!item) { - throw new Error('item is null or undefined'); + throw new Error('missing item'); } if (this.exists(item.id)) { this.removeItem(item.id); diff --git a/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts b/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts index 7340337b..c753619c 100644 --- a/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts +++ b/src/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.ts @@ -14,7 +14,7 @@ export enum SelectionType { export class SelectionTypeHandler { constructor(private readonly state: ICategoryCollectionState) { - if (!state) { throw new Error('undefined state'); } + if (!state) { throw new Error('missing state'); } } public selectType(type: SelectionType) { diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue index 294f93ad..fd8d2877 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/SelectableTree.vue @@ -67,7 +67,7 @@ export default class SelectableTree extends Vue { // Stateless to make it easier @Watch('initialNodes', { immediate: true }) public async updateNodes(nodes: readonly INode[]) { if (!nodes) { - throw new Error('undefined initial nodes'); + throw new Error('missing initial nodes'); } const initialNodes = nodes.map((node) => toNewLiquorTreeNode(node)); if (this.selectedNodeIds) { diff --git a/src/presentation/components/Shared/Throttle.ts b/src/presentation/components/Shared/Throttle.ts index 3c80cf5f..9a800960 100644 --- a/src/presentation/components/Shared/Throttle.ts +++ b/src/presentation/components/Shared/Throttle.ts @@ -38,10 +38,10 @@ class Throttler implements IThrottler { private readonly waitInMs: number, private readonly callback: CallbackType, ) { - if (!timer) { throw new Error('undefined timer'); } - if (!waitInMs) { throw new Error('no delay to throttle'); } + if (!timer) { throw new Error('missing timer'); } + if (!waitInMs) { throw new Error('missing delay'); } if (waitInMs < 0) { throw new Error('negative delay'); } - if (!callback) { throw new Error('undefined callback'); } + if (!callback) { throw new Error('missing callback'); } } public invoke(...args: unknown[]): void { diff --git a/tests/unit/application/ApplicationFactory.spec.ts b/tests/unit/application/ApplicationFactory.spec.ts index 751c542e..7535d70e 100644 --- a/tests/unit/application/ApplicationFactory.spec.ts +++ b/tests/unit/application/ApplicationFactory.spec.ts @@ -2,17 +2,20 @@ import 'mocha'; import { expect } from 'chai'; import { ApplicationFactory, ApplicationGetterType } from '@/application/ApplicationFactory'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ApplicationFactory', () => { describe('ctor', () => { - it('throws if getter is undefined', () => { - // arrange - const expectedError = 'undefined getter'; - const getter = undefined; - // act - const act = () => new SystemUnderTest(getter); - // assert - expect(act).to.throw(expectedError); + describe('throws if getter is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing getter'; + const getter: ApplicationGetterType = absentValue; + // act + const act = () => new SystemUnderTest(getter); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('getApp', () => { diff --git a/tests/unit/application/Common/Array.spec.ts b/tests/unit/application/Common/Array.spec.ts index b45268ce..b7ff9678 100644 --- a/tests/unit/application/Common/Array.spec.ts +++ b/tests/unit/application/Common/Array.spec.ts @@ -1,20 +1,35 @@ import 'mocha'; import { expect } from 'chai'; import { scrambledEqual, sequenceEqual } from '@/application/Common/Array'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; import { ComparerTestScenario } from './Array.ComparerTestScenario'; describe('Array', () => { describe('scrambledEqual', () => { - describe('throws if arguments are undefined', () => { - it('first argument is undefined', () => { - const expectedError = 'undefined first array'; - const act = () => scrambledEqual(undefined, []); - expect(act).to.throw(expectedError); + describe('throws if arguments are absent', () => { + describe('first argument is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing first array'; + const firstArray = absentValue; + const secondArray = []; + // act + const act = () => scrambledEqual(firstArray, secondArray); + // assert + expect(act).to.throw(expectedError); + }); }); - it('second arguments is undefined', () => { - const expectedError = 'undefined second array'; - const act = () => scrambledEqual([], undefined); - expect(act).to.throw(expectedError); + describe('second argument is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing second array'; + const firstArray = []; + const secondArray = absentValue; + // act + const act = () => scrambledEqual(firstArray, secondArray); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('returns as expected', () => { @@ -35,16 +50,30 @@ describe('Array', () => { }); }); describe('sequenceEqual', () => { - describe('throws if arguments are undefined', () => { - it('first argument is undefined', () => { - const expectedError = 'undefined first array'; - const act = () => sequenceEqual(undefined, []); - expect(act).to.throw(expectedError); + describe('throws if arguments are absent', () => { + describe('first argument is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing first array'; + const firstArray = absentValue; + const secondArray = []; + // act + const act = () => sequenceEqual(firstArray, secondArray); + // assert + expect(act).to.throw(expectedError); + }); }); - it('second arguments is undefined', () => { - const expectedError = 'undefined second array'; - const act = () => sequenceEqual([], undefined); - expect(act).to.throw(expectedError); + describe('second argument is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing second array'; + const firstArray = []; + const secondArray = absentValue; + // act + const act = () => sequenceEqual(firstArray, secondArray); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('returns as expected', () => { diff --git a/tests/unit/application/Common/Enum.spec.ts b/tests/unit/application/Common/Enum.spec.ts index 830e3798..e499def4 100644 --- a/tests/unit/application/Common/Enum.spec.ts +++ b/tests/unit/application/Common/Enum.spec.ts @@ -4,6 +4,7 @@ import { getEnumNames, getEnumValues, createEnumParser, assertInRange, } from '@/application/Common/Enum'; import { scrambledEqual } from '@/application/Common/Array'; +import { AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; import { EnumRangeTestRunner } from './EnumRangeTestRunner'; describe('Enum', () => { @@ -37,16 +38,11 @@ describe('Enum', () => { // arrange const enumName = 'ParsableEnum'; const testCases = [ - { - name: 'undefined', - value: undefined, - expectedError: `undefined ${enumName}`, - }, - { - name: 'empty', - value: '', - expectedError: `undefined ${enumName}`, - }, + ...AbsentStringTestCases.map((test) => ({ + name: test.valueName, + value: test.absentValue, + expectedError: `missing ${enumName}`, + })), { name: 'out of range', value: 'value3', @@ -105,7 +101,7 @@ describe('Enum', () => { // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows() + .testAbsentValueThrows() .testValidValueDoesNotThrow(validValue); }); }); diff --git a/tests/unit/application/Common/EnumRangeTestRunner.ts b/tests/unit/application/Common/EnumRangeTestRunner.ts index a5ca569b..aaf4be50 100644 --- a/tests/unit/application/Common/EnumRangeTestRunner.ts +++ b/tests/unit/application/Common/EnumRangeTestRunner.ts @@ -1,6 +1,7 @@ import 'mocha'; import { expect } from 'chai'; import { EnumType } from '@/application/Common/Enum'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; export class EnumRangeTestRunner { constructor(private readonly runner: (value: TEnumValue) => void) { @@ -19,15 +20,17 @@ export class EnumRangeTestRunner { return this; } - public testUndefinedValueThrows() { - it('throws when value is undefined', () => { - // arrange - const value = undefined; - const expectedError = 'undefined enum value'; - // act - const act = () => this.runner(value); - // assert - expect(act).to.throw(expectedError); + public testAbsentValueThrows() { + describe('throws when value is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const value = absentValue; + const expectedError = 'absent enum value'; + // act + const act = () => this.runner(value); + // assert + expect(act).to.throw(expectedError); + }); }); return this; } @@ -45,7 +48,7 @@ export class EnumRangeTestRunner { } public testValidValueDoesNotThrow(validValue: TEnumValue) { - it('throws when value is undefined', () => { + it('does not throw with valid value', () => { // arrange const value = validValue; // act diff --git a/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactory.spec.ts b/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactory.spec.ts index 3e0bedf0..3c44b2ec 100644 --- a/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactory.spec.ts +++ b/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactory.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory'; import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; import { ScriptingLanguageFactoryTestRunner } from './ScriptingLanguageFactoryTestRunner'; class ScriptingLanguageConcrete extends ScriptingLanguageFactory { @@ -16,32 +17,34 @@ describe('ScriptingLanguageFactory', () => { describe('validates language', () => { // arrange const validValue = ScriptingLanguage.batchfile; - const getter = () => undefined; + const getter = () => 1; const sut = new ScriptingLanguageConcrete(); // act const act = (language: ScriptingLanguage) => sut.registerGetter(language, getter); // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows() + .testAbsentValueThrows() .testValidValueDoesNotThrow(validValue); }); - it('throw when getter is undefined', () => { - // arrange - const expectedError = 'undefined getter'; - const language = ScriptingLanguage.batchfile; - const getter = undefined; - const sut = new ScriptingLanguageConcrete(); - // act - const act = () => sut.registerGetter(language, getter); - // assert - expect(act).to.throw(expectedError); + describe('describe when getter is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing getter'; + const language = ScriptingLanguage.batchfile; + const getter = absentValue; + const sut = new ScriptingLanguageConcrete(); + // act + const act = () => sut.registerGetter(language, getter); + // assert + expect(act).to.throw(expectedError); + }); }); it('throw when language is already registered', () => { // arrange const language = ScriptingLanguage.batchfile; const expectedError = `${ScriptingLanguage[language]} is already registered`; - const getter = () => undefined; + const getter = () => 1; const sut = new ScriptingLanguageConcrete(); // act sut.registerGetter(language, getter); @@ -51,9 +54,12 @@ describe('ScriptingLanguageFactory', () => { }); }); describe('create', () => { + // arrange const sut = new ScriptingLanguageConcrete(); - sut.registerGetter(ScriptingLanguage.batchfile, () => undefined); const runner = new ScriptingLanguageFactoryTestRunner(); + // act + sut.registerGetter(ScriptingLanguage.batchfile, () => 1); + // assert runner.testCreateMethod(sut); }); }); diff --git a/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner.ts b/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner.ts index a35f1c68..eb840af6 100644 --- a/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner.ts +++ b/tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner.ts @@ -13,7 +13,7 @@ export class ScriptingLanguageFactoryTestRunner { } public testCreateMethod(sut: IScriptingLanguageFactory) { - if (!sut) { throw new Error('undefined sut'); } + if (!sut) { throw new Error('missing sut'); } testLanguageValidation(sut); testExpectedInstanceTypes(sut, this.expectedTypes); } @@ -46,7 +46,7 @@ function testLanguageValidation(sut: IScriptingLanguageFactory) { // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows() + .testAbsentValueThrows() .testValidValueDoesNotThrow(validValue); }); } diff --git a/tests/unit/application/Context/ApplicationContext.spec.ts b/tests/unit/application/Context/ApplicationContext.spec.ts index 240580df..0f03d128 100644 --- a/tests/unit/application/Context/ApplicationContext.spec.ts +++ b/tests/unit/application/Context/ApplicationContext.spec.ts @@ -8,6 +8,7 @@ import { IApplication } from '@/domain/IApplication'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ApplicationContext', () => { describe('changeContext', () => { @@ -125,18 +126,33 @@ describe('ApplicationContext', () => { expect(duplicates.length).to.be.equal(0); }); }); + describe('throws with invalid os', () => { + new EnumRangeTestRunner((os: OperatingSystem) => { + // arrange + const sut = new ObservableApplicationContextFactory() + .construct(); + // act + sut.changeContext(os); + }) + // assert + .testOutOfRangeThrows() + .testAbsentValueThrows() + .testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application'); + }); }); describe('ctor', () => { describe('app', () => { - it('throw when app is undefined', () => { - // arrange - const expectedError = 'undefined app'; - const app = undefined; - const os = OperatingSystem.Windows; - // act - const act = () => new ApplicationContext(app, os); - // assert - expect(act).to.throw(expectedError); + describe('throw when app is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing app'; + const app = absentValue; + const os = OperatingSystem.Windows; + // act + const act = () => new ApplicationContext(app, os); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('collection', () => { @@ -188,7 +204,7 @@ describe('ApplicationContext', () => { // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows() + .testAbsentValueThrows() .testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application'); }); }); diff --git a/tests/unit/application/Context/ApplicationContextFactory.spec.ts b/tests/unit/application/Context/ApplicationContextFactory.spec.ts index 525ba9a1..234d619e 100644 --- a/tests/unit/application/Context/ApplicationContextFactory.spec.ts +++ b/tests/unit/application/Context/ApplicationContextFactory.spec.ts @@ -8,6 +8,7 @@ import { IApplication } from '@/domain/IApplication'; import { EnvironmentStub } from '@tests/unit/stubs/EnvironmentStub'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; +import { expectThrowsAsync } from '@tests/unit/shared/Assertions/ExpectThrowsAsync'; describe('ApplicationContextFactory', () => { describe('buildContext', () => { @@ -23,6 +24,15 @@ describe('ApplicationContextFactory', () => { // assert expect(expected).to.equal(context.app); }); + it('throws when null', async () => { + // arrange + const expectedError = 'missing factory'; + const factory = null; + // act + const act = async () => { await buildContext(factory); }; + // assert + expectThrowsAsync(act, expectedError); + }); }); describe('environment', () => { describe('sets initial OS as expected', () => { @@ -69,6 +79,16 @@ describe('ApplicationContextFactory', () => { expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`); }); }); + it('throws when null', async () => { + // arrange + const expectedError = 'missing environment'; + const factory = mockFactoryWithApp(undefined); + const environment = null; + // act + const act = async () => { await buildContext(factory, environment); }; + // assert + expectThrowsAsync(act, expectedError); + }); }); }); }); diff --git a/tests/unit/application/Context/State/Code/ApplicationCode.spec.ts b/tests/unit/application/Context/State/Code/ApplicationCode.spec.ts index 19d0ccd3..955fca49 100644 --- a/tests/unit/application/Context/State/Code/ApplicationCode.spec.ts +++ b/tests/unit/application/Context/State/Code/ApplicationCode.spec.ts @@ -13,6 +13,8 @@ import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionSt import { CategoryStub } from '@tests/unit/stubs/CategoryStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; +import { UserSelectionStub } from '@tests/unit/stubs/UserSelectionStub'; describe('ApplicationCode', () => { describe('ctor', () => { @@ -46,6 +48,41 @@ describe('ApplicationCode', () => { // assert expect(actual).to.equal(expected.code); }); + describe('throws when userSelection is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing userSelection'; + const userSelection = absentValue; + const definition = new ScriptingDefinitionStub(); + // act + const act = () => new ApplicationCode(userSelection, definition); + // assert + expect(act).to.throw(expectedError); + }); + }); + describe('throws when scriptingDefinition is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing scriptingDefinition'; + const userSelection = new UserSelectionStub([]); + const definition = absentValue; + // act + const act = () => new ApplicationCode(userSelection, definition); + // assert + expect(act).to.throw(expectedError); + }); + }); + it('throws when generator is missing', () => { + // arrange + const expectedError = 'missing generator'; + const userSelection = new UserSelectionStub([]); + const definition = new ScriptingDefinitionStub(); + const generator = null; + // act + const act = () => new ApplicationCode(userSelection, definition, generator); + // assert + expect(act).to.throw(expectedError); + }); }); describe('changed event', () => { describe('code', () => { diff --git a/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts b/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts index 83c61615..73925b33 100644 --- a/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts +++ b/tests/unit/application/Context/State/Code/Generation/UserScriptGenerator.spec.ts @@ -6,6 +6,8 @@ import { ICodeBuilderFactory } from '@/application/Context/State/Code/Generation import { ICodeBuilder } from '@/application/Context/State/Code/Generation/ICodeBuilder'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub'; import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; +import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub'; describe('UserScriptGenerator', () => { describe('scriptingDefinition', () => { @@ -18,8 +20,7 @@ describe('UserScriptGenerator', () => { .withCode('code\nmulti-lined') .toSelectedScript(); const definition = new ScriptingDefinitionStub() - .withStartCode(startCode) - .withEndCode(undefined); + .withStartCode(startCode); const expectedStart = `${startCode}\n`; // act const code = sut.buildCode([script], definition); @@ -27,24 +28,25 @@ describe('UserScriptGenerator', () => { const actual = code.code; expect(actual.startsWith(expectedStart)); }); - it('is not prepended if empty', () => { - // arrange - const codeBuilderStub = new CodeBuilderStub(); - const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub)); - const script = new ScriptStub('id') - .withCode('code\nmulti-lined') - .toSelectedScript(); - const definition = new ScriptingDefinitionStub() - .withStartCode(undefined) - .withEndCode(undefined); - const expectedStart = codeBuilderStub - .appendFunction(script.script.name, script.script.code.execute) - .toString(); - // act - const code = sut.buildCode([script], definition); - // assert - const actual = code.code; - expect(actual.startsWith(expectedStart)); + describe('is not prepended if empty', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const codeBuilderStub = new CodeBuilderStub(); + const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub)); + const script = new ScriptStub('id') + .withCode('code\nmulti-lined') + .toSelectedScript(); + const definition = new ScriptingDefinitionStub() + .withStartCode(absentValue); + const expectedStart = codeBuilderStub + .appendFunction(script.script.name, script.script.code.execute) + .toString(); + // act + const code = sut.buildCode([script], definition); + // assert + const actual = code.code; + expect(actual.startsWith(expectedStart)); + }); }); }); describe('endCode', () => { @@ -64,23 +66,38 @@ describe('UserScriptGenerator', () => { const actual = code.code; expect(actual.endsWith(expectedEnd)); }); - it('is not appended if empty', () => { + describe('is not appended if empty', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const codeBuilderStub = new CodeBuilderStub(); + const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub)); + const script = new ScriptStub('id') + .withCode('code\nmulti-lined') + .toSelectedScript(); + const expectedEnd = codeBuilderStub + .appendFunction(script.script.name, script.script.code.execute) + .toString(); + const definition = new ScriptingDefinitionStub() + .withEndCode(absentValue); + // act + const code = sut.buildCode([script], definition); + // assert + const actual = code.code; + expect(actual.endsWith(expectedEnd)); + }); + }); + }); + describe('throws when absent', () => { + itEachAbsentObjectValue((absentValue) => { // arrange - const codeBuilderStub = new CodeBuilderStub(); - const sut = new UserScriptGenerator(mockCodeBuilderFactory(codeBuilderStub)); - const script = new ScriptStub('id') - .withCode('code\nmulti-lined') - .toSelectedScript(); - const expectedEnd = codeBuilderStub - .appendFunction(script.script.name, script.script.code.execute) - .toString(); - const definition = new ScriptingDefinitionStub() - .withEndCode(undefined); + const expectedError = 'missing definition'; + const sut = new UserScriptGenerator(); + const scriptingDefinition = absentValue; + const selectedScripts = [new SelectedScriptStub('a')]; // act - const code = sut.buildCode([script], definition); + const act = () => sut.buildCode(selectedScripts, scriptingDefinition); // assert - const actual = code.code; - expect(actual.endsWith(expectedEnd)); + expect(act).to.throw(expectedError); }); }); }); @@ -200,6 +217,21 @@ describe('UserScriptGenerator', () => { }); }); }); + describe('selectedScripts', () => { + describe('throws when absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing scripts'; + const sut = new UserScriptGenerator(); + const scriptingDefinition = new ScriptingDefinitionStub(); + const selectedScripts = absentValue; + // act + const act = () => sut.buildCode(selectedScripts, scriptingDefinition); + // assert + expect(act).to.throw(expectedError); + }); + }); + }); }); function mockCodeBuilderFactory(mock: ICodeBuilder): ICodeBuilderFactory { diff --git a/tests/unit/application/Environment/BrowserOs/BrowserOsDetector.spec.ts b/tests/unit/application/Environment/BrowserOs/BrowserOsDetector.spec.ts index b91969a5..c279d0e5 100644 --- a/tests/unit/application/Environment/BrowserOs/BrowserOsDetector.spec.ts +++ b/tests/unit/application/Environment/BrowserOs/BrowserOsDetector.spec.ts @@ -2,17 +2,21 @@ import 'mocha'; import { expect } from 'chai'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { BrowserOsDetector } from '@/application/Environment/BrowserOs/BrowserOsDetector'; +import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; import { BrowserOsTestCases } from './BrowserOsTestCases'; describe('BrowserOsDetector', () => { - it('returns undefined when user agent is undefined', () => { - // arrange - const expected = undefined; - const sut = new BrowserOsDetector(); - // act - const actual = sut.detect(undefined); - // assert - expect(actual).to.equal(expected); + describe('returns undefined when user agent is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expected = undefined; + const userAgent = absentValue; + const sut = new BrowserOsDetector(); + // act + const actual = sut.detect(userAgent); + // assert + expect(actual).to.equal(expected); + }); }); it('detects as expected', () => { BrowserOsTestCases.forEach((testCase) => { diff --git a/tests/unit/application/Parser/ApplicationParser.spec.ts b/tests/unit/application/Parser/ApplicationParser.spec.ts index bd40ac36..f54b1e66 100644 --- a/tests/unit/application/Parser/ApplicationParser.spec.ts +++ b/tests/unit/application/Parser/ApplicationParser.spec.ts @@ -13,6 +13,7 @@ import { getEnumValues } from '@/application/Common/Enum'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; import { getProcessEnvironmentStub } from '@tests/unit/stubs/ProcessEnvironmentStub'; import { CollectionDataStub } from '@tests/unit/stubs/CollectionDataStub'; +import { getAbsentCollectionTestCases, AbsentObjectTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ApplicationParser', () => { describe('parseApplication', () => { @@ -112,21 +113,23 @@ describe('ApplicationParser', () => { describe('throws when data is invalid', () => { // arrange const testCases = [ - { - expectedError: 'no collection provided', - data: [], - }, - { - expectedError: 'undefined collection provided', - data: [new CollectionDataStub(), undefined], - }, + ...getAbsentCollectionTestCases().map((testCase) => ({ + name: `given absent collection "${testCase.valueName}"`, + value: testCase.absentValue, + expectedError: 'missing collections', + })).filter((test) => test.value !== undefined /* the default value is set */), + ...AbsentObjectTestCases.map((testCase) => ({ + name: `given absent item "${testCase.valueName}"`, + value: [testCase.absentValue], + expectedError: 'missing collection provided', + })), ]; for (const testCase of testCases) { - it(testCase.expectedError, () => { + it(testCase.name, () => { const parserMock = new CategoryCollectionParserSpy().mockParser(); const env = getProcessEnvironmentStub(); // act - const act = () => parseApplication(parserMock, env, testCase.data); + const act = () => parseApplication(parserMock, env, testCase.value); // assert expect(act).to.throw(testCase.expectedError); }); diff --git a/tests/unit/application/Parser/CategoryCollectionParser.spec.ts b/tests/unit/application/Parser/CategoryCollectionParser.spec.ts index f915bfa3..234e3e68 100644 --- a/tests/unit/application/Parser/CategoryCollectionParser.spec.ts +++ b/tests/unit/application/Parser/CategoryCollectionParser.spec.ts @@ -15,30 +15,35 @@ import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub'; import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub'; import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub'; import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CategoryCollectionParser', () => { describe('parseCategoryCollection', () => { - it('throws when undefined', () => { - // arrange - const expectedError = 'content is null or undefined'; - const info = new ProjectInformationStub(); - // act - const act = () => parseCategoryCollection(undefined, info); - // assert - expect(act).to.throw(expectedError); - }); - describe('actions', () => { - it('throws when undefined actions', () => { + describe('throws with absent content', () => { + itEachAbsentObjectValue((absentValue) => { // arrange - const expectedError = 'content does not define any action'; - const collection = new CollectionDataStub() - .withActions(undefined); + const expectedError = 'missing content'; const info = new ProjectInformationStub(); // act - const act = () => parseCategoryCollection(collection, info); + const act = () => parseCategoryCollection(absentValue, info); // assert expect(act).to.throw(expectedError); }); + }); + describe('actions', () => { + describe('throws with absent actions', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'content does not define any action'; + const collection = new CollectionDataStub() + .withActions(absentValue); + const info = new ProjectInformationStub(); + // act + const act = () => parseCategoryCollection(collection, info); + // assert + expect(act).to.throw(expectedError); + }); + }); it('throws when has no actions', () => { // arrange const expectedError = 'content does not define any action'; diff --git a/tests/unit/application/Parser/CategoryParser.spec.ts b/tests/unit/application/Parser/CategoryParser.spec.ts index ebd2e0cb..0436d078 100644 --- a/tests/unit/application/Parser/CategoryParser.spec.ts +++ b/tests/unit/application/Parser/CategoryParser.spec.ts @@ -8,53 +8,44 @@ import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub'; import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCollectionParseContextStub'; import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub'; import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub'; +import { itEachAbsentCollectionValue, itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CategoryParser', () => { describe('parseCategory', () => { describe('invalid category', () => { - it('throws when undefined', () => { - // arrange - const expectedMessage = 'category is null or undefined'; - const category = undefined; - const context = new CategoryCollectionParseContextStub(); - // act - const act = () => parseCategory(category, context); - // assert - expect(act).to.throw(expectedMessage); + describe('throws when category data is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedMessage = 'missing category'; + const category = absentValue; + const context = new CategoryCollectionParseContextStub(); + // act + const act = () => parseCategory(category, context); + // assert + expect(act).to.throw(expectedMessage); + }); }); - it('throws when children are empty', () => { - // arrange - const categoryName = 'test'; - const expectedMessage = `category has no children: "${categoryName}"`; - const category = new CategoryDataStub() - .withName(categoryName) - .withChildren([]); - const context = new CategoryCollectionParseContextStub(); - // act - const act = () => parseCategory(category, context); - // assert - expect(act).to.throw(expectedMessage); - }); - it('throws when children are undefined', () => { - // arrange - const categoryName = 'test'; - const expectedMessage = `category has no children: "${categoryName}"`; - const category = new CategoryDataStub() - .withName(categoryName) - .withChildren(undefined); - const context = new CategoryCollectionParseContextStub(); - // act - const act = () => parseCategory(category, context); - // assert - expect(act).to.throw(expectedMessage); - }); - it('throws when name is empty or undefined', () => { - // arrange - const expectedMessage = 'category has no name'; - const invalidNames = ['', undefined]; - invalidNames.forEach((invalidName) => { + describe('throws when category children is absent', () => { + itEachAbsentCollectionValue((absentValue) => { + // arrange + const categoryName = 'test'; + const expectedMessage = `category has no children: "${categoryName}"`; const category = new CategoryDataStub() - .withName(invalidName); + .withName(categoryName) + .withChildren(absentValue); + const context = new CategoryCollectionParseContextStub(); + // act + const act = () => parseCategory(category, context); + // assert + expect(act).to.throw(expectedMessage); + }); + }); + describe('throws when name is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedMessage = 'category has no name'; + const category = new CategoryDataStub() + .withName(absentValue); const context = new CategoryCollectionParseContextStub(); // act const act = () => parseCategory(category, context); @@ -63,15 +54,17 @@ describe('CategoryParser', () => { }); }); }); - it('throws when context is undefined', () => { - // arrange - const expectedError = 'undefined context'; - const context = undefined; - const category = new CategoryDataStub(); - // act - const act = () => parseCategory(category, context); - // assert - expect(act).to.throw(expectedError); + describe('throws when context is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing context'; + const context = absentValue; + const category = new CategoryDataStub(); + // act + const act = () => parseCategory(category, context); + // assert + expect(act).to.throw(expectedError); + }); }); it('returns expected docs', () => { // arrange diff --git a/tests/unit/application/Parser/DocumentationParser.spec.ts b/tests/unit/application/Parser/DocumentationParser.spec.ts index a41a5fd1..6bdd800e 100644 --- a/tests/unit/application/Parser/DocumentationParser.spec.ts +++ b/tests/unit/application/Parser/DocumentationParser.spec.ts @@ -2,11 +2,19 @@ import 'mocha'; import { expect } from 'chai'; import { DocumentableData } from 'js-yaml-loader!@/*'; import { parseDocUrls } from '@/application/Parser/DocumentationParser'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('DocumentationParser', () => { describe('parseDocUrls', () => { - it('throws when undefined', () => { - expect(() => parseDocUrls(undefined)).to.throw('documentable is null or undefined'); + describe('throws when absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing documentable'; + // act + const act = () => parseDocUrls(absentValue); + // assert + expect(act).to.throw(expectedError); + }); }); it('returns empty when empty', () => { // arrange diff --git a/tests/unit/application/Parser/Script/CategoryCollectionParseContext.spec.ts b/tests/unit/application/Parser/Script/CategoryCollectionParseContext.spec.ts index bef987cc..9932ea5f 100644 --- a/tests/unit/application/Parser/Script/CategoryCollectionParseContext.spec.ts +++ b/tests/unit/application/Parser/Script/CategoryCollectionParseContext.spec.ts @@ -1,6 +1,5 @@ import 'mocha'; import { expect } from 'chai'; -import { FunctionData } from 'js-yaml-loader!@/*'; import { ISyntaxFactory } from '@/application/Parser/Script/Syntax/ISyntaxFactory'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { CategoryCollectionParseContext } from '@/application/Parser/Script/CategoryCollectionParseContext'; @@ -9,32 +8,36 @@ import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompi import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub'; import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub'; import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub'; +import { itEachAbsentCollectionValue, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CategoryCollectionParseContext', () => { describe('ctor', () => { describe('functionsData', () => { - it('can create with empty values', () => { - // arrange - const testData: FunctionData[][] = [undefined, []]; - const scripting = new ScriptingDefinitionStub(); - for (const functionsData of testData) { + describe('can create with absent data', () => { + itEachAbsentCollectionValue((absentValue) => { + // arrange + const scripting = new ScriptingDefinitionStub(); + // act + const act = () => new CategoryCollectionParseContext(absentValue, scripting); + // assert + expect(act).to.not.throw(); + }); + }); + }); + describe('scripting', () => { + describe('throws when missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing scripting'; + const scripting = absentValue; + const functionsData = [FunctionDataStub.createWithCode()]; // act const act = () => new CategoryCollectionParseContext(functionsData, scripting); // assert - expect(act).to.not.throw(); - } + expect(act).to.throw(expectedError); + }); }); }); - it('scripting', () => { - // arrange - const expectedError = 'undefined scripting'; - const scripting = undefined; - const functionsData = [FunctionDataStub.createWithCode()]; - // act - const act = () => new CategoryCollectionParseContext(functionsData, scripting); - // assert - expect(act).to.throw(expectedError); - }); }); describe('compiler', () => { it('constructed as expected', () => { diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts index 6b8dead3..ff895b9c 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/Expression.spec.ts @@ -10,20 +10,24 @@ import { ExpressionEvaluationContextStub } from '@tests/unit/stubs/ExpressionEva import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler'; import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub'; import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection'; +import { AbsentObjectTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; +import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext'; describe('Expression', () => { describe('ctor', () => { describe('position', () => { - it('throws if undefined', () => { - // arrange - const expectedError = 'undefined position'; - const position = undefined; - // act - const act = () => new ExpressionBuilder() - .withPosition(position) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws when missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing position'; + const position = absentValue; + // act + const act = () => new ExpressionBuilder() + .withPosition(position) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); it('sets as expected', () => { // arrange @@ -37,17 +41,19 @@ describe('Expression', () => { }); }); describe('parameters', () => { - it('defaults to empty array if undefined', () => { - // arrange - const parameters = undefined; - // act - const actual = new ExpressionBuilder() - .withParameters(parameters) - .build(); - // assert - expect(actual.parameters); - expect(actual.parameters.all); - expect(actual.parameters.all.length).to.equal(0); + describe('defaults to empty array if absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const parameters = absentValue; + // act + const actual = new ExpressionBuilder() + .withParameters(parameters) + .build(); + // assert + expect(actual.parameters); + expect(actual.parameters.all); + expect(actual.parameters.all.length).to.equal(0); + }); }); it('sets as expected', () => { // arrange @@ -63,37 +69,44 @@ describe('Expression', () => { }); }); describe('evaluator', () => { - it('throws if undefined', () => { - // arrange - const expectedError = 'undefined evaluator'; - const evaluator = undefined; - // act - const act = () => new ExpressionBuilder() - .withEvaluator(evaluator) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws if missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing evaluator'; + const evaluator = absentValue; + // act + const act = () => new ExpressionBuilder() + .withEvaluator(evaluator) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); }); }); describe('evaluate', () => { describe('throws with invalid arguments', () => { - const testCases = [ - { - name: 'throws if arguments is undefined', - context: undefined, - expectedError: 'undefined context', - }, + const testCases: readonly { + name: string, + context: IExpressionEvaluationContext, + expectedError: string, + sutBuilder?: (builder: ExpressionBuilder) => ExpressionBuilder, + }[] = [ + ...AbsentObjectTestCases.map((testCase) => ({ + name: `throws if arguments is ${testCase.valueName}`, + context: testCase.absentValue, + expectedError: 'missing context', + })), { name: 'throws when some of the required args are not provided', - sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false), + sutBuilder: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false), context: new ExpressionEvaluationContextStub() .withArgs(new FunctionCallArgumentCollectionStub().withArgument('b', 'provided')), expectedError: 'argument values are provided for required parameters: "a", "c"', }, { name: 'throws when none of the required args are not provided', - sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false), + sutBuilder: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false), context: new ExpressionEvaluationContextStub() .withArgs(new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated')), expectedError: 'argument values are provided for required parameters: "a", "b"', @@ -103,8 +116,8 @@ describe('Expression', () => { it(testCase.name, () => { // arrange const sutBuilder = new ExpressionBuilder(); - if (testCase.sut) { - testCase.sut(sutBuilder); + if (testCase.sutBuilder) { + testCase.sutBuilder(sutBuilder); } const sut = sutBuilder.build(); // act diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.spec.ts index ba94dde4..144e05db 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext.spec.ts @@ -5,19 +5,23 @@ import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Sc import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler'; import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub'; import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ExpressionEvaluationContext', () => { describe('ctor', () => { describe('args', () => { - it('throws if args are undefined', () => { - // arrange - const expectedError = 'undefined args'; - const builder = new ExpressionEvaluationContextBuilder() - .withArgs(undefined); - // act - const act = () => builder.build(); - // assert - expect(act).throw(expectedError); + describe('throws if args is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing args, send empty collection instead.'; + const args = absentValue; + // act + const act = () => new ExpressionEvaluationContextBuilder() + .withArgs(args) + .build(); + // assert + expect(act).throw(expectedError); + }); }); it('sets as expected', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.spec.ts index 447eb783..f46e901b 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler.spec.ts @@ -5,30 +5,21 @@ import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressi import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub'; import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub'; import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ExpressionsCompiler', () => { describe('compileExpressions', () => { - describe('returns code when it is empty or undefined', () => { - // arrange - const testCases = [{ - name: 'empty', - value: '', - }, { - name: 'undefined', - value: undefined, - }, - ]; - for (const test of testCases) { - it(`given ${test.name}`, () => { - const expected = test.value; - const sut = new SystemUnderTest(); - const args = new FunctionCallArgumentCollectionStub(); - // act - const value = sut.compileExpressions(test.value, args); - // assert - expect(value).to.equal(expected); - }); - } + describe('returns code when it is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expected = absentValue; + const sut = new SystemUnderTest(); + const args = new FunctionCallArgumentCollectionStub(); + // act + const value = sut.compileExpressions(absentValue, args); + // assert + expect(value).to.equal(expected); + }); }); describe('combines expressions as expected', () => { // arrange @@ -100,16 +91,18 @@ describe('ExpressionsCompiler', () => { expect(expressions[1].callHistory).to.have.lengthOf(1); expect(expressions[1].callHistory[0].args).to.equal(expected); }); - it('throws if arguments is undefined', () => { - // arrange - const expectedError = 'undefined args, send empty collection instead'; - const args = undefined; - const expressionParserMock = new ExpressionParserStub(); - const sut = new SystemUnderTest(expressionParserMock); - // act - const act = () => sut.compileExpressions('code', args); - // assert - expect(act).to.throw(expectedError); + describe('throws if arguments is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing args, send empty collection instead.'; + const args = absentValue; + const expressionParserMock = new ExpressionParserStub(); + const sut = new SystemUnderTest(expressionParserMock); + // act + const act = () => sut.compileExpressions('code', args); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('throws when expected argument is not provided but used in code', () => { diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.spec.ts index 2453fb98..8518a4c5 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.spec.ts @@ -4,18 +4,30 @@ import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Ex import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser'; import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser'; import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CompositeExpressionParser', () => { describe('ctor', () => { - it('throws if one of the parsers is undefined', () => { + it('throws if null parsers given', () => { // arrange - const expectedError = 'undefined leaf'; - const parsers: readonly IExpressionParser[] = [undefined, mockParser()]; + const expectedError = 'missing leafs'; + const parsers = null; // act const act = () => new CompositeExpressionParser(parsers); // assert expect(act).to.throw(expectedError); }); + describe('throws if one of the parsers is undefined', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing leaf'; + const parsers: readonly IExpressionParser[] = [absentValue, mockParser()]; + // act + const act = () => new CompositeExpressionParser(parsers); + // assert + expect(act).to.throw(expectedError); + }); + }); }); describe('findExpressions', () => { describe('returns result from parsers as expected', () => { diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts index edd0e70c..4de2c6d4 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser.spec.ts @@ -4,32 +4,20 @@ import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expres import { IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/Regex/RegexParser'; import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition'; import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub'; +import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('RegexParser', () => { describe('findExpressions', () => { - describe('throws when code is unexpected', () => { - // arrange - const testCases = [ - { - name: 'undefined', - value: undefined, - expectedError: 'undefined code', - }, - { - name: 'empty', - value: '', - expectedError: 'undefined code', - }, - ]; - for (const testCase of testCases) { - it(`given ${testCase.name}`, () => { - const sut = new RegexParserConcrete(/unimportant/); - // act - const act = () => sut.findExpressions(testCase.value); - // assert - expect(act).to.throw(testCase.expectedError); - }); - } + describe('throws when code is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing code'; + const sut = new RegexParserConcrete(/unimportant/); + // act + const act = () => sut.findExpressions(absentValue); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws when position is invalid', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.spec.ts index 9e277420..28a266eb 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes.spec.ts @@ -1,5 +1,6 @@ import 'mocha'; import { EscapeDoubleQuotes } from '@/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes'; +import { AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; import { runPipeTests } from './PipeTestRunner'; describe('EscapeDoubleQuotes', () => { @@ -22,10 +23,10 @@ describe('EscapeDoubleQuotes', () => { input: '""hello world""', expectedOutput: '"^"""^""hello world"^"""^""', }, - { - name: 'returns undefined when if input is undefined', - input: undefined, - expectedOutput: undefined, - }, + ...AbsentStringTestCases.map((testCase) => ({ + name: 'returns as it is when if input is missing', + input: testCase.absentValue, + expectedOutput: testCase.absentValue, + })), ]); }); diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.spec.ts index 307e0617..5ea4d3c6 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory.spec.ts @@ -2,6 +2,7 @@ import 'mocha'; import { expect } from 'chai'; import { PipeFactory } from '@/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory'; import { PipeStub } from '@tests/unit/stubs/PipeStub'; +import { AbsentStringTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('PipeFactory', () => { describe('ctor', () => { @@ -19,10 +20,21 @@ describe('PipeFactory', () => { // expect expect(act).to.throw(expectedError); }); - it('throws when a pipe is undefined', () => { + describe('throws when a pipe is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing pipe in list'; + const pipes = [new PipeStub(), absentValue]; + // act + const act = () => new PipeFactory(pipes); + // expect + expect(act).to.throw(expectedError); + }); + }); + it('throws when pipes are null', () => { // arrange - const expectedError = 'undefined pipe in list'; - const pipes = [new PipeStub(), undefined]; + const expectedError = 'missing pipes'; + const pipes = null; // act const act = () => new PipeFactory(pipes); // expect @@ -70,44 +82,34 @@ describe('PipeFactory', () => { function testPipeNameValidation(testRunner: (invalidName: string) => void) { const testCases = [ - { - exceptionBuilder: () => 'empty pipe name', - values: [null, undefined, ''], - }, - { - exceptionBuilder: (name: string) => `Pipe name should be camelCase: "${name}"`, - values: [ - 'PascalCase', - 'snake-case', - 'includesNumb3rs', - 'includes Whitespace', - 'noSpec\'ial', - ], - }, + // Validate missing value + ...AbsentStringTestCases.map((testCase) => ({ + name: `empty pipe name (${testCase.valueName})`, + value: testCase.absentValue, + expectedError: 'empty pipe name', + })), + // Validate camelCase + ...[ + 'PascalCase', + 'snake-case', + 'includesNumb3rs', + 'includes Whitespace', + 'noSpec\'ial', + ].map((nonCamelCaseValue) => ({ + name: `non camel case value (${nonCamelCaseValue})`, + value: nonCamelCaseValue, + expectedError: `Pipe name should be camelCase: "${nonCamelCaseValue}"`, + })), ]; for (const testCase of testCases) { - for (const invalidName of testCase.values) { - it(`invalid name (${printValue(invalidName)}) throws`, () => { - // arrange - const expectedError = testCase.exceptionBuilder(invalidName); - // act - const act = () => testRunner(invalidName); - // expect - expect(act).to.throw(expectedError); - }); - } - } -} - -function printValue(value: string) { - switch (value) { - case undefined: - return 'undefined'; - case null: - return 'null'; - case '': - return 'empty'; - default: - return value; + it(testCase.name, () => { + // arrange + const invalidName = testCase.value; + const { expectedError } = testCase; + // act + const act = () => testRunner(invalidName); + // expect + expect(act).to.throw(expectedError); + }); } } diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.spec.ts index 5657239d..5011828b 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/Pipes/PipelineCompiler.spec.ts @@ -5,6 +5,7 @@ import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressi import { IPipeFactory } from '@/application/Parser/Script/Compiler/Expressions/Pipes/PipeFactory'; import { PipeStub } from '@tests/unit/stubs/PipeStub'; import { PipeFactoryStub } from '@tests/unit/stubs/PipeFactoryStub'; +import { AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; describe('PipelineCompiler', () => { describe('compile', () => { @@ -15,26 +16,16 @@ describe('PipelineCompiler', () => { expectedError: string; } const testCases: ITestCase[] = [ - { - name: '"value" is empty', - act: (test) => test.withValue(''), - expectedError: 'undefined value', - }, - { - name: '"value" is undefined', - act: (test) => test.withValue(undefined), - expectedError: 'undefined value', - }, - { - name: '"pipeline" is empty', - act: (test) => test.withPipeline(''), - expectedError: 'undefined pipeline', - }, - { - name: '"pipeline" is undefined', - act: (test) => test.withPipeline(undefined), - expectedError: 'undefined pipeline', - }, + ...AbsentStringTestCases.map((testCase) => ({ + name: `"value" is ${testCase.valueName}`, + act: (test) => test.withValue(testCase.absentValue), + expectedError: 'missing value', + })), + ...AbsentStringTestCases.map((testCase) => ({ + name: `"pipeline" is ${testCase.valueName}`, + act: (test) => test.withPipeline(testCase.absentValue), + expectedError: 'missing pipeline', + })), { name: '"pipeline" does not start with pipe', act: (test) => test.withPipeline('pipeline |'), diff --git a/tests/unit/application/Parser/Script/Compiler/Expressions/SyntaxParsers/WithParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Expressions/SyntaxParsers/WithParser.spec.ts index 947171bb..6702bd07 100644 --- a/tests/unit/application/Parser/Script/Compiler/Expressions/SyntaxParsers/WithParser.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Expressions/SyntaxParsers/WithParser.spec.ts @@ -1,6 +1,7 @@ import 'mocha'; import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition'; import { WithParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/WithParser'; +import { AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; import { SyntaxParserTestsRunner } from './SyntaxParserTestsRunner'; describe('WithParser', () => { @@ -84,20 +85,13 @@ describe('WithParser', () => { describe('renders scope conditionally', () => { describe('does not render scope if argument is undefined', () => { runner.expectResults( - { - name: 'does not render when value is undefined', + ...AbsentStringTestCases.map((testCase) => ({ + name: `does not render when value is "${testCase.valueName}"`, code: '{{ with $parameter }}dark{{ end }} ', args: (args) => args - .withArgument('parameter', undefined), + .withArgument('parameter', testCase.absentValue), expected: [''], - }, - { - name: 'does not render when value is empty', - code: '{{ with $parameter }}dark {{.}}{{ end }}', - args: (args) => args - .withArgument('parameter', ''), - expected: [''], - }, + })), { name: 'does not render when argument is not provided', code: '{{ with $parameter }}dark{{ end }}', diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts index e56a13d1..35ab48d0 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts @@ -1,6 +1,7 @@ import 'mocha'; import { expect } from 'chai'; import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument'; +import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; import { testParameterName } from '../../../ParameterNameTestRunner'; describe('FunctionCallArgument', () => { @@ -13,18 +14,20 @@ describe('FunctionCallArgument', () => { .parameterName, ); }); - it('throws if argument value is undefined', () => { - // arrange - const parameterName = 'paramName'; - const expectedError = `undefined argument value for "${parameterName}"`; - const argumentValue = undefined; - // act - const act = () => new FunctionCallArgumentBuilder() - .withParameterName(parameterName) - .withArgumentValue(argumentValue) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws if argument value is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const parameterName = 'paramName'; + const expectedError = `missing argument value for "${parameterName}"`; + const argumentValue = absentValue; + // act + const act = () => new FunctionCallArgumentBuilder() + .withParameterName(parameterName) + .withArgumentValue(argumentValue) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); }); }); diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts index d4994ef7..c0ddc103 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts @@ -2,18 +2,21 @@ import 'mocha'; import { expect } from 'chai'; import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection'; import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('FunctionCallArgumentCollection', () => { describe('addArgument', () => { - it('throws if argument is undefined', () => { - // arrange - const errorMessage = 'undefined argument'; - const arg = undefined; - const sut = new FunctionCallArgumentCollection(); - // act - const act = () => sut.addArgument(arg); - // assert - expect(act).to.throw(errorMessage); + describe('throws if argument is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const errorMessage = 'missing argument'; + const arg = absentValue; + const sut = new FunctionCallArgumentCollection(); + // act + const act = () => sut.addArgument(arg); + // assert + expect(act).to.throw(errorMessage); + }); }); it('throws if parameter value is already provided', () => { // arrange @@ -58,17 +61,17 @@ describe('FunctionCallArgumentCollection', () => { }); }); describe('getArgument', () => { - it('throws if parameter name is undefined', () => { - // arrange - const expectedError = 'undefined parameter name'; - const undefinedValues = ['', undefined]; - for (const undefinedValue of undefinedValues) { + describe('throws if parameter name is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing parameter name'; const sut = new FunctionCallArgumentCollection(); + const parameterName = absentValue; // act - const act = () => sut.getArgument(undefinedValue); + const act = () => sut.getArgument(parameterName); // assert expect(act).to.throw(expectedError); - } + }); }); it('throws if argument does not exist', () => { // arrange @@ -94,17 +97,17 @@ describe('FunctionCallArgumentCollection', () => { }); }); describe('hasArgument', () => { - it('throws if parameter name is undefined', () => { - // arrange - const expectedError = 'undefined parameter name'; - const undefinedValues = ['', undefined]; - for (const undefinedValue of undefinedValues) { + describe('throws if parameter name is missing', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing parameter name'; + const parameterName = absentValue; const sut = new FunctionCallArgumentCollection(); // act - const act = () => sut.hasArgument(undefinedValue); + const act = () => sut.hasArgument(parameterName); // assert expect(act).to.throw(expectedError); - } + }); }); describe('returns as expected', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.spec.ts index 5e156776..901a94ee 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler.spec.ts @@ -10,35 +10,40 @@ import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub'; import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub'; import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub'; import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('FunctionCallCompiler', () => { describe('compileCall', () => { describe('parameter validation', () => { describe('call', () => { - it('throws with undefined call', () => { - // arrange - const expectedError = 'undefined calls'; - const call = undefined; - const functions = new SharedFunctionCollectionStub(); - const sut = new MockableFunctionCallCompiler(); - // act - const act = () => sut.compileCall(call, functions); - // assert - expect(act).to.throw(expectedError); + describe('throws with missing call', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing calls'; + const call = absentValue; + const functions = new SharedFunctionCollectionStub(); + const sut = new MockableFunctionCallCompiler(); + // act + const act = () => sut.compileCall(call, functions); + // assert + expect(act).to.throw(expectedError); + }); }); - it('throws if call sequence has undefined call', () => { - // arrange - const expectedError = 'undefined function call'; - const call = [ - new FunctionCallStub(), - undefined, - ]; - const functions = new SharedFunctionCollectionStub(); - const sut = new MockableFunctionCallCompiler(); - // act - const act = () => sut.compileCall(call, functions); - // assert - expect(act).to.throw(expectedError); + describe('throws if call sequence has absent call', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing function call'; + const call = [ + new FunctionCallStub(), + absentValue, + ]; + const functions = new SharedFunctionCollectionStub(); + const sut = new MockableFunctionCallCompiler(); + // act + const act = () => sut.compileCall(call, functions); + // assert + expect(act).to.throw(expectedError); + }); }); describe('throws if call parameters does not match function parameters', () => { // arrange @@ -109,16 +114,18 @@ describe('FunctionCallCompiler', () => { }); }); describe('functions', () => { - it('throws with undefined functions', () => { - // arrange - const expectedError = 'undefined functions'; - const call = new FunctionCallStub(); - const functions = undefined; - const sut = new MockableFunctionCallCompiler(); - // act - const act = () => sut.compileCall([call], functions); - // assert - expect(act).to.throw(expectedError); + describe('throws with missing functions', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing functions'; + const call = new FunctionCallStub(); + const functions = absentValue; + const sut = new MockableFunctionCallCompiler(); + // act + const act = () => sut.compileCall([call], functions); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws if function does not exist', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCall.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCall.spec.ts index 69ff4d1a..0a95668a 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCall.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCall.spec.ts @@ -3,20 +3,23 @@ import { expect } from 'chai'; import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall'; import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection'; import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('FunctionCall', () => { describe('ctor', () => { describe('args', () => { - it('throws when args is undefined', () => { - // arrange - const expectedError = 'undefined args'; - const args = undefined; - // act - const act = () => new FunctionCallBuilder() - .withArgs(args) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws when args is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing args'; + const args = absentValue; + // act + const act = () => new FunctionCallBuilder() + .withArgs(args) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); it('sets args as expected', () => { // arrange @@ -31,16 +34,18 @@ describe('FunctionCall', () => { }); }); describe('functionName', () => { - it('throws when function name is undefined', () => { - // arrange - const expectedError = 'empty function name in function call'; - const functionName = undefined; - // act - const act = () => new FunctionCallBuilder() - .withFunctionName(functionName) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws when function name is missing', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing function name in function call'; + const functionName = absentValue; + // act + const act = () => new FunctionCallBuilder() + .withFunctionName(functionName) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); it('sets function name as expected', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.spec.ts index 5dbb7418..023f3e4f 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Call/FunctionCallParser.spec.ts @@ -2,17 +2,20 @@ import 'mocha'; import { expect } from 'chai'; import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCallParser'; import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('FunctionCallParser', () => { describe('parseFunctionCalls', () => { - it('throws with undefined call', () => { - // arrange - const expectedError = 'undefined call data'; - const call = undefined; - // act - const act = () => parseFunctionCalls(call); - // assert - expect(act).to.throw(expectedError); + describe('throws with missing call data', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing call data'; + const call = absentValue; + // act + const act = () => parseFunctionCalls(call); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws if call is not an object', () => { // arrange @@ -25,29 +28,33 @@ describe('FunctionCallParser', () => { expect(act).to.throw(expectedError); }); }); - it('throws if call sequence has undefined call', () => { - // arrange - const expectedError = 'undefined function call'; - const data = [ - new FunctionCallDataStub(), - undefined, - ]; - // act - const act = () => parseFunctionCalls(data); - // assert - expect(act).to.throw(expectedError); + describe('throws if call sequence has undefined call', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing call data'; + const data = [ + new FunctionCallDataStub(), + absentValue, + ]; + // act + const act = () => parseFunctionCalls(data); + // assert + expect(act).to.throw(expectedError); + }); }); - it('throws if call sequence has undefined function name', () => { - // arrange - const expectedError = 'empty function name in function call'; - const data = [ - new FunctionCallDataStub().withName('function-name'), - new FunctionCallDataStub().withName(undefined), - ]; - // act - const act = () => parseFunctionCalls(data); - // assert - expect(act).to.throw(expectedError); + describe('throws if call sequence has undefined function name', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing function name in function call'; + const data = [ + new FunctionCallDataStub().withName('function-name'), + new FunctionCallDataStub().withName(absentValue), + ]; + // act + const act = () => parseFunctionCalls(data); + // assert + expect(act).to.throw(expectedError); + }); }); it('parses single call as expected', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts index 5b7bd8cc..64e17811 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection.spec.ts @@ -2,6 +2,7 @@ import 'mocha'; import { expect } from 'chai'; import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection'; import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('FunctionParameterCollection', () => { it('all returns added parameters as expected', () => { @@ -34,15 +35,17 @@ describe('FunctionParameterCollection', () => { expect(act).to.throw(expectedError); }); describe('addParameter', () => { - it('throws if parameter is undefined', () => { - // arrange - const expectedError = 'undefined parameter'; - const value = undefined; - const sut = new FunctionParameterCollection(); - // act - const act = () => sut.addParameter(value); - // assert - expect(act).to.throw(expectedError); + describe('throws if parameter is undefined', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing parameter'; + const value = absentValue; + const sut = new FunctionParameterCollection(); + // act + const act = () => sut.addParameter(value); + // assert + expect(act).to.throw(expectedError); + }); }); }); }); diff --git a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunction.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunction.spec.ts index 8e97f807..f4d9aec6 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunction.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunction.spec.ts @@ -6,6 +6,10 @@ import { createCallerFunction, createFunctionWithInlineCode } from '@/applicatio import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall'; import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub'; import { FunctionBodyType, ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction'; +import { + AbsentStringTestCases, itEachAbsentCollectionValue, itEachAbsentObjectValue, + itEachAbsentStringValue, +} from '@tests/unit/shared/TestCases/AbsentTests'; describe('SharedFunction', () => { describe('name', () => { @@ -20,18 +24,17 @@ describe('SharedFunction', () => { // assert expect(sut.name).equal(expected); }); - it('throws if empty or undefined', () => { - // arrange - const expectedError = 'undefined function name'; - const invalidValues = [undefined, '']; - for (const invalidValue of invalidValues) { + it('throws when absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing function name'; const builder = new SharedFunctionBuilder() - .withName(invalidValue); + .withName(absentValue); // act const act = () => build(builder); // assert expect(act).to.throw(expectedError); - } + }); }); }); }); @@ -48,16 +51,18 @@ describe('SharedFunction', () => { // assert expect(sut.parameters).equal(expected); }); - it('throws if undefined', () => { - // arrange - const expectedError = 'undefined parameters'; - const parameters = undefined; - const builder = new SharedFunctionBuilder() - .withParameters(parameters); - // act - const act = () => build(builder); - // assert - expect(act).to.throw(expectedError); + describe('throws if missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing parameters'; + const parameters = absentValue; + const builder = new SharedFunctionBuilder() + .withParameters(parameters); + // act + const act = () => build(builder); + // assert + expect(act).to.throw(expectedError); + }); }); }); }); @@ -74,12 +79,12 @@ describe('SharedFunction', () => { // assert expect(sut.body.code.do).equal(expected); }); - it('throws if empty or undefined', () => { - // arrange - const functionName = 'expected-function-name'; - const expectedError = `undefined code in function "${functionName}"`; - const invalidValues = [undefined, '']; - for (const invalidValue of invalidValues) { + describe('throws if absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const functionName = 'expected-function-name'; + const expectedError = `undefined code in function "${functionName}"`; + const invalidValue = absentValue; // act const act = () => new SharedFunctionBuilder() .withName(functionName) @@ -87,13 +92,16 @@ describe('SharedFunction', () => { .createFunctionWithInlineCode(); // assert expect(act).to.throw(expectedError); - } + }); }); }); describe('revertCode', () => { it('sets as expected', () => { // arrange - const testData = ['expected-revert-code', undefined, '']; + const testData = [ + 'expected-revert-code', + ...AbsentStringTestCases.map((testCase) => testCase.absentValue), + ]; for (const data of testData) { // act const sut = new SharedFunctionBuilder() @@ -138,31 +146,20 @@ describe('SharedFunction', () => { // assert expect(sut.body.calls).equal(expected); }); - it('throws if undefined', () => { - // arrange - const functionName = 'invalidFunction'; - const callSequence = undefined; - const expectedError = `undefined call sequence in function "${functionName}"`; - // act - const act = () => new SharedFunctionBuilder() - .withName(functionName) - .withCallSequence(callSequence) - .createCallerFunction(); - // assert - expect(act).to.throw(expectedError); - }); - it('throws if empty', () => { - // arrange - const functionName = 'invalidFunction'; - const callSequence = []; - const expectedError = `empty call sequence in function "${functionName}"`; - // act - const act = () => new SharedFunctionBuilder() - .withName(functionName) - .withCallSequence(callSequence) - .createCallerFunction(); - // assert - expect(act).to.throw(expectedError); + describe('throws if missing', () => { + itEachAbsentCollectionValue((absentValue) => { + // arrange + const functionName = 'invalidFunction'; + const callSequence = absentValue; + const expectedError = `missing call sequence in function "${functionName}"`; + // act + const act = () => new SharedFunctionBuilder() + .withName(functionName) + .withCallSequence(callSequence) + .createCallerFunction(); + // assert + expect(act).to.throw(expectedError); + }); }); }); it('sets type as expected', () => { diff --git a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts index eb106567..b6fa9a15 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionCollection.spec.ts @@ -4,18 +4,21 @@ import { SharedFunctionCollection } from '@/application/Parser/Script/Compiler/F import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub'; import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction'; import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('SharedFunctionCollection', () => { describe('addFunction', () => { - it('throws if function is undefined', () => { - // arrange - const expectedError = 'undefined function'; - const func = undefined; - const sut = new SharedFunctionCollection(); - // act - const act = () => sut.addFunction(func); - // assert - expect(act).to.throw(expectedError); + describe('throws if function is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing function'; + const func = absentValue; + const sut = new SharedFunctionCollection(); + // act + const act = () => sut.addFunction(func); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws if function with same name already exists', () => { // arrange @@ -32,18 +35,16 @@ describe('SharedFunctionCollection', () => { }); }); describe('getFunctionByName', () => { - it('throws if name is undefined', () => { - // arrange - const expectedError = 'undefined function name'; - const invalidValues = [undefined, '']; - const sut = new SharedFunctionCollection(); - for (const invalidValue of invalidValues) { - const name = invalidValue; + describe('throws if name is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing function name'; + const sut = new SharedFunctionCollection(); // act - const act = () => sut.getFunctionByName(name); + const act = () => sut.getFunctionByName(absentValue); // assert expect(act).to.throw(expectedError); - } + }); }); it('throws if function does not exist', () => { // arrange diff --git a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts index 89ea52da..5ecc1516 100644 --- a/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/Function/SharedFunctionsParser.spec.ts @@ -7,19 +7,22 @@ import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub'; import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub'; import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter'; import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub'; +import { itEachAbsentCollectionValue, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('SharedFunctionsParser', () => { describe('parseFunctions', () => { describe('validates functions', () => { - it('throws if one of the functions is undefined', () => { - // arrange - const expectedError = 'some functions are undefined'; - const functions = [FunctionDataStub.createWithCode(), undefined]; - const sut = new SharedFunctionsParser(); - // act - const act = () => sut.parseFunctions(functions); - // assert - expect(act).to.throw(expectedError); + describe('throws if one of the functions is undefined', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'some functions are undefined'; + const functions = [FunctionDataStub.createWithCode(), absentValue]; + const sut = new SharedFunctionsParser(); + // act + const act = () => sut.parseFunctions(functions); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws when functions have same names', () => { // arrange @@ -143,17 +146,14 @@ describe('SharedFunctionsParser', () => { expect(act).to.throw(expectedError); }); }); - describe('empty functions', () => { - it('returns empty collection', () => { + describe('given empty functions, returns empty collection', () => { + itEachAbsentCollectionValue((absentValue) => { // arrange - const emptyValues = [[], undefined]; const sut = new SharedFunctionsParser(); - for (const emptyFunctions of emptyValues) { - // act - const actual = sut.parseFunctions(emptyFunctions); - // assert - expect(actual).to.not.equal(undefined); - } + // act + const actual = sut.parseFunctions(absentValue); + // assert + expect(actual).to.not.equal(undefined); }); }); describe('function with inline code', () => { diff --git a/tests/unit/application/Parser/Script/Compiler/ParameterNameTestRunner.ts b/tests/unit/application/Parser/Script/Compiler/ParameterNameTestRunner.ts index 644e2fd3..f06476e0 100644 --- a/tests/unit/application/Parser/Script/Compiler/ParameterNameTestRunner.ts +++ b/tests/unit/application/Parser/Script/Compiler/ParameterNameTestRunner.ts @@ -1,5 +1,6 @@ import 'mocha'; import { expect } from 'chai'; +import { AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; export function testParameterName(action: (parameterName: string) => string) { describe('name', () => { @@ -22,16 +23,11 @@ export function testParameterName(action: (parameterName: string) => string) { describe('throws if invalid', () => { // arrange const testCases = [ - { - name: 'undefined', - value: undefined, - expectedError: 'undefined parameter name', - }, - { - name: 'empty', - value: '', - expectedError: 'undefined parameter name', - }, + ...AbsentStringTestCases.map((test) => ({ + name: test.valueName, + value: test.absentValue, + expectedError: 'missing parameter name', + })), { name: 'has @', value: 'b@d', diff --git a/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts b/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts index 3fce0198..91088199 100644 --- a/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts +++ b/tests/unit/application/Parser/Script/Compiler/ScriptCompiler.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { expect } from 'chai'; import { FunctionData } from 'js-yaml-loader!@/*'; -import { ILanguageSyntax } from '@/domain/ScriptCode'; +import { ILanguageSyntax, ScriptCode } from '@/domain/ScriptCode'; import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler'; import { ISharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionsParser'; import { ICompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/ICompiledCode'; @@ -14,33 +14,39 @@ import { SharedFunctionsParserStub } from '@tests/unit/stubs/SharedFunctionsPars import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub'; import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCallParser'; import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ScriptCompiler', () => { describe('ctor', () => { - it('throws if syntax is undefined', () => { - // arrange - const expectedError = 'undefined syntax'; - // act - const act = () => new ScriptCompilerBuilder() - .withSomeFunctions() - .withSyntax(undefined) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws if syntax is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing syntax'; + const syntax = absentValue; + // act + const act = () => new ScriptCompilerBuilder() + .withSomeFunctions() + .withSyntax(syntax) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); }); describe('canCompile', () => { - it('throws if script is undefined', () => { - // arrange - const expectedError = 'undefined script'; - const argument = undefined; - const builder = new ScriptCompilerBuilder() - .withEmptyFunctions() - .build(); - // act - const act = () => builder.canCompile(argument); - // assert - expect(act).to.throw(expectedError); + describe('throws if script is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing script'; + const argument = absentValue; + const builder = new ScriptCompilerBuilder() + .withEmptyFunctions() + .build(); + // act + const act = () => builder.canCompile(argument); + // assert + expect(act).to.throw(expectedError); + }); }); it('returns true if "call" is defined', () => { // arrange @@ -66,17 +72,19 @@ describe('ScriptCompiler', () => { }); }); describe('compile', () => { - it('throws if script is undefined', () => { - // arrange - const expectedError = 'undefined script'; - const argument = undefined; - const builder = new ScriptCompilerBuilder() - .withEmptyFunctions() - .build(); - // act - const act = () => builder.compile(argument); - // assert - expect(act).to.throw(expectedError); + describe('throws if script is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing script'; + const argument = absentValue; + const builder = new ScriptCompilerBuilder() + .withEmptyFunctions() + .build(); + // act + const act = () => builder.compile(argument); + // assert + expect(act).to.throw(expectedError); + }); }); it('returns code as expected', () => { // arrange @@ -148,15 +156,21 @@ describe('ScriptCompiler', () => { it('rethrows error from ScriptCode with script name', () => { // arrange const scriptName = 'scriptName'; - const expectedError = `Script "${scriptName}" code is empty or undefined`; + const syntax = new LanguageSyntaxStub(); + const invalidCode: ICompiledCode = { code: undefined, revertCode: undefined }; + const realExceptionMessage = collectExceptionMessage( + () => new ScriptCode(invalidCode.code, invalidCode.revertCode, syntax), + ); + const expectedError = `Script "${scriptName}" ${realExceptionMessage}`; const callCompiler: IFunctionCallCompiler = { - compileCall: () => ({ code: undefined, revertCode: undefined }), + compileCall: () => invalidCode, }; const scriptData = ScriptDataStub.createWithCall() .withName(scriptName); const sut = new ScriptCompilerBuilder() .withSomeFunctions() .withFunctionCallCompiler(callCompiler) + .withSyntax(syntax) .build(); // act const act = () => sut.compile(scriptData); @@ -230,3 +244,13 @@ class ScriptCompilerBuilder { ); } } + +function collectExceptionMessage(action: () => unknown) { + let message = ''; + try { + action(); + } catch (e) { + message = e.message; + } + return message; +} diff --git a/tests/unit/application/Parser/Script/ScriptParser.spec.ts b/tests/unit/application/Parser/Script/ScriptParser.spec.ts index 09ce63d1..dd30dec5 100644 --- a/tests/unit/application/Parser/Script/ScriptParser.spec.ts +++ b/tests/unit/application/Parser/Script/ScriptParser.spec.ts @@ -10,6 +10,7 @@ import { EnumParserStub } from '@tests/unit/stubs/EnumParserStub'; import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub'; import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCollectionParseContextStub'; import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ScriptParser', () => { describe('parseScript', () => { @@ -37,15 +38,17 @@ describe('ScriptParser', () => { expect(actual.documentationUrls).to.deep.equal(expected); }); describe('invalid script', () => { - it('throws when script is undefined', () => { - // arrange - const expectedError = 'undefined script'; - const parseContext = new CategoryCollectionParseContextStub(); - const script = undefined; - // act - const act = () => parseScript(script, parseContext); - // assert - expect(act).to.throw(expectedError); + describe('throws when script is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing script'; + const parseContext = new CategoryCollectionParseContextStub(); + const script = absentValue; + // act + const act = () => parseScript(script, parseContext); + // assert + expect(act).to.throw(expectedError); + }); }); it('throws when both function call and code are defined', () => { // arrange @@ -83,13 +86,12 @@ describe('ScriptParser', () => { }); }); describe('level', () => { - it('accepts undefined level', () => { - const undefinedLevels: string[] = ['', undefined]; - undefinedLevels.forEach((undefinedLevel) => { + describe('accepts absent level', () => { + itEachAbsentStringValue((absentValue) => { // arrange const parseContext = new CategoryCollectionParseContextStub(); const script = ScriptDataStub.createWithCode() - .withRecommend(undefinedLevel); + .withRecommend(absentValue); // act const actual = parseScript(script, parseContext); // assert @@ -140,15 +142,17 @@ describe('ScriptParser', () => { expect(actual).to.equal(expected); }); describe('compiler', () => { - it('throws when context is not defined', () => { - // arrange - const expectedMessage = 'undefined context'; - const script = ScriptDataStub.createWithCode(); - const context: ICategoryCollectionParseContext = undefined; - // act - const act = () => parseScript(script, context); - // assert - expect(act).to.throw(expectedMessage); + describe('throws when context is not defined', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedMessage = 'missing context'; + const script = ScriptDataStub.createWithCode(); + const context: ICategoryCollectionParseContext = absentValue; + // act + const act = () => parseScript(script, context); + // assert + expect(act).to.throw(expectedMessage); + }); }); it('gets code from compiler', () => { // arrange diff --git a/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts b/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts index 61806b24..e31e875d 100644 --- a/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts +++ b/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts @@ -4,29 +4,36 @@ import { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSu import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler'; import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub'; import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub'; +import { AbsentObjectTestCases, AbsentStringTestCases } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CodeSubstituter', () => { describe('throws with invalid parameters', () => { // arrange - const testCases = [{ - expectedError: 'undefined code', - parameters: { - code: undefined, - info: new ProjectInformationStub(), - }, - }, - { - expectedError: 'undefined info', - parameters: { - code: 'non empty code', - info: undefined, - }, - }]; + const testCases = [ + ...AbsentStringTestCases.map((testCase) => ({ + name: `given code: ${testCase.valueName}`, + expectedError: 'missing code', + parameters: { + code: testCase.absentValue, + info: new ProjectInformationStub(), + }, + })), + ...AbsentObjectTestCases.map((testCase) => ({ + name: `given info: ${testCase.valueName}`, + expectedError: 'missing info', + parameters: { + code: 'non empty code', + info: testCase.absentValue, + }, + })), + ]; for (const testCase of testCases) { - it(`throws "${testCase.expectedError}" as expected`, () => { + it(`${testCase.name} throws "${testCase.expectedError}"`, () => { + // arrange const sut = new CodeSubstituterBuilder().build(); + const { code, info } = testCase.parameters; // act - const act = () => sut.substitute(testCase.parameters.code, testCase.parameters.info); + const act = () => sut.substitute(code, info); // assert expect(act).to.throw(testCase.expectedError); }); diff --git a/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts b/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts index 75fe6e37..2386383a 100644 --- a/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts +++ b/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts @@ -9,30 +9,37 @@ import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub import { EnumParserStub } from '@tests/unit/stubs/EnumParserStub'; import { ScriptingDefinitionDataStub } from '@tests/unit/stubs/ScriptingDefinitionDataStub'; import { CodeSubstituterStub } from '@tests/unit/stubs/CodeSubstituterStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ScriptingDefinitionParser', () => { describe('parseScriptingDefinition', () => { - it('throws when info is undefined', () => { - // arrange - const info = undefined; - const definition = new ScriptingDefinitionDataStub(); - const sut = new ScriptingDefinitionParserBuilder() - .build(); - // act - const act = () => sut.parse(definition, info); - // assert - expect(act).to.throw('undefined info'); + describe('throws when info is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing info'; + const info = absentValue; + const definition = new ScriptingDefinitionDataStub(); + const sut = new ScriptingDefinitionParserBuilder() + .build(); + // act + const act = () => sut.parse(definition, info); + // assert + expect(act).to.throw(expectedError); + }); }); - it('throws when definition is undefined', () => { - // arrange - const info = new ProjectInformationStub(); - const definition = undefined; - const sut = new ScriptingDefinitionParserBuilder() - .build(); - // act - const act = () => sut.parse(definition, info); - // assert - expect(act).to.throw('undefined definition'); + describe('throws when definition is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing definition'; + const info = new ProjectInformationStub(); + const definition = absentValue; + const sut = new ScriptingDefinitionParserBuilder() + .build(); + // act + const act = () => sut.parse(definition, info); + // assert + expect(act).to.throw(expectedError); + }); }); describe('language', () => { it('parses as expected', () => { diff --git a/tests/unit/domain/Application.spec.ts b/tests/unit/domain/Application.spec.ts index 9b5bac42..177c1bb7 100644 --- a/tests/unit/domain/Application.spec.ts +++ b/tests/unit/domain/Application.spec.ts @@ -4,6 +4,8 @@ import { Application } from '@/domain/Application'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub'; +import { AbsentObjectTestCases, getAbsentCollectionTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; +import { ICategoryCollection } from '@/domain/ICategoryCollection'; describe('Application', () => { describe('getCollection', () => { @@ -33,15 +35,17 @@ describe('Application', () => { }); describe('ctor', () => { describe('info', () => { - it('throws if undefined', () => { - // arrange - const expectedError = 'undefined project information'; - const info = undefined; - const collections = [new CategoryCollectionStub()]; - // act - const act = () => new Application(info, collections); - // assert - expect(act).to.throw(expectedError); + describe('throws if missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing project information'; + const info = absentValue; + const collections = [new CategoryCollectionStub()]; + // act + const act = () => new Application(info, collections); + // assert + expect(act).to.throw(expectedError); + }); }); it('sets as expected', () => { // arrange @@ -56,22 +60,21 @@ describe('Application', () => { describe('collections', () => { describe('throws on invalid value', () => { // arrange - const testCases = [ - { - name: 'undefined', - expectedError: 'undefined collections', - value: undefined, - }, - { - name: 'empty', - expectedError: 'no collection in the list', - value: [], - }, - { - name: 'undefined value in list', - expectedError: 'undefined collection in the list', - value: [new CategoryCollectionStub(), undefined], - }, + const testCases: readonly { + name: string, + expectedError: string, + value: readonly ICategoryCollection[], + }[] = [ + ...getAbsentCollectionTestCases().map((testCase) => ({ + name: testCase.valueName, + expectedError: 'missing collections', + value: testCase.absentValue, + })), + ...AbsentObjectTestCases.map((testCase) => ({ + name: `${testCase.valueName} value in list`, + expectedError: 'missing collection in the list', + value: [new CategoryCollectionStub(), testCase.absentValue], + })), { name: 'two collections with same OS', expectedError: 'multiple collections with same os: windows', @@ -83,12 +86,14 @@ describe('Application', () => { }, ]; for (const testCase of testCases) { - const info = new ProjectInformationStub(); - const collections = testCase.value; - // act - const act = () => new Application(info, collections); - // assert - expect(act).to.throw(testCase.expectedError); + it(testCase.name, () => { + const info = new ProjectInformationStub(); + const collections = testCase.value; + // act + const act = () => new Application(info, collections); + // assert + expect(act).to.throw(testCase.expectedError); + }); } }); it('sets as expected', () => { diff --git a/tests/unit/domain/Category.spec.ts b/tests/unit/domain/Category.spec.ts index 20329850..c5d8371a 100644 --- a/tests/unit/domain/Category.spec.ts +++ b/tests/unit/domain/Category.spec.ts @@ -3,13 +3,20 @@ import { expect } from 'chai'; import { Category } from '@/domain/Category'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub'; +import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('Category', () => { describe('ctor', () => { - it('throws when name is empty', () => { - const expectedError = 'undefined or empty name'; - const construct = () => new Category(5, '', [], [new CategoryStub(5)], []); - expect(construct).to.throw(expectedError); + describe('throws when name is absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing name'; + const name = absentValue; + // act + const construct = () => new Category(5, name, [], [new CategoryStub(5)], []); + // assert + expect(construct).to.throw(expectedError); + }); }); it('throws when has no children', () => { const expectedError = 'A category must have at least one sub-category or script'; diff --git a/tests/unit/domain/CategoryCollection.spec.ts b/tests/unit/domain/CategoryCollection.spec.ts index c2e8b159..2121b76d 100644 --- a/tests/unit/domain/CategoryCollection.spec.ts +++ b/tests/unit/domain/CategoryCollection.spec.ts @@ -10,6 +10,7 @@ import { CategoryCollection } from '@/domain/CategoryCollection'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub'; import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('CategoryCollection', () => { describe('getScriptsByLevel', () => { @@ -72,24 +73,18 @@ describe('CategoryCollection', () => { // assert expect(expected).to.deep.equal(actual); }); - it('throws when level is undefined', () => { - // arrange - const sut = new CategoryCollectionBuilder() - .construct(); - // act - const act = () => sut.getScriptsByLevel(undefined); + describe('throws when given invalid level', () => { + new EnumRangeTestRunner((level) => { + // arrange + const sut = new CategoryCollectionBuilder() + .construct(); + // act + sut.getScriptsByLevel(level); + }) // assert - expect(act).to.throw('undefined level'); - }); - it('throws when level is out of range', () => { - // arrange - const invalidValue = 66; - const sut = new CategoryCollectionBuilder() - .construct(); - // act - const act = () => sut.getScriptsByLevel(invalidValue); - // assert - expect(act).to.throw(`invalid level: ${invalidValue}`); + .testAbsentValueThrows() + .testOutOfRangeThrows() + .testValidValueDoesNotThrow(RecommendationLevel.Standard); }); }); describe('actions', () => { @@ -221,7 +216,7 @@ describe('CategoryCollection', () => { // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows(); + .testAbsentValueThrows(); }); }); describe('scriptingDefinition', () => { @@ -235,17 +230,20 @@ describe('CategoryCollection', () => { // assert expect(sut.scripting).to.deep.equal(expected); }); - it('cannot construct without initial script', () => { - // arrange - const scriptingDefinition = undefined; - // act - function construct() { - return new CategoryCollectionBuilder() - .withScripting(scriptingDefinition) - .construct(); - } - // assert - expect(construct).to.throw('undefined scripting definition'); + describe('cannot construct without initial script', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing scripting definition'; + const scriptingDefinition = absentValue; + // act + function construct() { + return new CategoryCollectionBuilder() + .withScripting(scriptingDefinition) + .construct(); + } + // assert + expect(construct).to.throw(expectedError); + }); }); }); }); diff --git a/tests/unit/domain/ProjectInformation.spec.ts b/tests/unit/domain/ProjectInformation.spec.ts index 074b696f..8ee4a32a 100644 --- a/tests/unit/domain/ProjectInformation.spec.ts +++ b/tests/unit/domain/ProjectInformation.spec.ts @@ -124,7 +124,7 @@ describe('ProjectInformation', () => { // assert new EnumRangeTestRunner(act) .testOutOfRangeThrows() - .testUndefinedValueThrows() + .testAbsentValueThrows() .testInvalidValueThrows(OperatingSystem.KaiOS, `Unsupported os: ${OperatingSystem[OperatingSystem.KaiOS]}`); }); }); diff --git a/tests/unit/domain/Script.spec.ts b/tests/unit/domain/Script.spec.ts index 47016471..96f03cd6 100644 --- a/tests/unit/domain/Script.spec.ts +++ b/tests/unit/domain/Script.spec.ts @@ -5,6 +5,7 @@ import { Script } from '@/domain/Script'; import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { IScriptCode } from '@/domain/IScriptCode'; import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('Script', () => { describe('ctor', () => { @@ -20,18 +21,20 @@ describe('Script', () => { // assert expect(actual).to.deep.equal(expected); }); - it('throws if undefined', () => { - // arrange - const name = 'script-name'; - const expectedError = `undefined code (script: ${name})`; - const code: IScriptCode = undefined; - // act - const construct = () => new ScriptBuilder() - .withName(name) - .withCode(code) - .build(); - // assert - expect(construct).to.throw(expectedError); + describe('throws when missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const name = 'script-name'; + const expectedError = `missing code (script: ${name})`; + const code: IScriptCode = absentValue; + // act + const construct = () => new ScriptBuilder() + .withName(name) + .withCode(code) + .build(); + // assert + expect(construct).to.throw(expectedError); + }); }); }); describe('canRevert', () => { @@ -114,7 +117,7 @@ class ScriptBuilder { private level = RecommendationLevel.Standard; - private documentationUrls: readonly string[] = undefined; + private documentationUrls: readonly string[]; public withCodes(code: string, revertCode = ''): ScriptBuilder { this.code = new ScriptCodeStub() diff --git a/tests/unit/domain/ScriptCode.spec.ts b/tests/unit/domain/ScriptCode.spec.ts index 67766827..0a6e34cc 100644 --- a/tests/unit/domain/ScriptCode.spec.ts +++ b/tests/unit/domain/ScriptCode.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { ScriptCode, ILanguageSyntax } from '@/domain/ScriptCode'; import { IScriptCode } from '@/domain/IScriptCode'; import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub'; +import { AbsentStringTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ScriptCode', () => { describe('code', () => { @@ -17,22 +18,14 @@ describe('ScriptCode', () => { }, expectedError: '(revert): Code itself and its reverting code cannot be the same', }, - { - name: 'cannot construct with undefined "execute"', + ...AbsentStringTestCases.map((testCase) => ({ + name: `cannot construct with ${testCase.valueName} "execute"`, code: { - execute: undefined, + execute: testCase.absentValue, revert: 'code', }, - expectedError: 'code is empty or undefined', - }, - { - name: 'cannot construct with empty "execute"', - code: { - execute: '', - revert: 'code', - }, - expectedError: 'code is empty or undefined', - }, + expectedError: 'missing code', + })), ]; for (const testCase of testCases) { it(testCase.name, () => { @@ -142,16 +135,18 @@ describe('ScriptCode', () => { }); }); describe('syntax', () => { - it('throws if undefined', () => { - // arrange - const expectedError = 'undefined syntax'; - const syntax = undefined; - // act - const act = () => new ScriptCodeBuilder() - .withSyntax(syntax) - .build(); - // assert - expect(act).to.throw(expectedError); + describe('throws if missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing syntax'; + const syntax = absentValue; + // act + const act = () => new ScriptCodeBuilder() + .withSyntax(syntax) + .build(); + // assert + expect(act).to.throw(expectedError); + }); }); }); }); diff --git a/tests/unit/domain/ScriptingDefinition.spec.ts b/tests/unit/domain/ScriptingDefinition.spec.ts index 4b03cf55..033a1eaf 100644 --- a/tests/unit/domain/ScriptingDefinition.spec.ts +++ b/tests/unit/domain/ScriptingDefinition.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { ScriptingDefinition } from '@/domain/ScriptingDefinition'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { getEnumValues } from '@/application/Common/Enum'; +import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('ScriptingDefinition', () => { describe('language', () => { @@ -64,18 +65,18 @@ describe('ScriptingDefinition', () => { // assert expect(sut.startCode).to.equal(expected); }); - it('throws when undefined', () => { - // arrange - const expectedError = 'undefined start code'; - const undefinedValues = ['', undefined]; - for (const undefinedValue of undefinedValues) { + describe('throws when absent', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing start code'; + const undefinedValue = absentValue; // act const act = () => new ScriptingDefinitionBuilder() .withStartCode(undefinedValue) .build(); // assert expect(act).to.throw(expectedError); - } + }); }); }); describe('endCode', () => { @@ -89,18 +90,18 @@ describe('ScriptingDefinition', () => { // assert expect(sut.endCode).to.equal(expected); }); - it('throws when undefined', () => { - // arrange - const expectedError = 'undefined end code'; - const undefinedValues = ['', undefined]; - for (const undefinedValue of undefinedValues) { + describe('throws when undefined', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing end code'; + const undefinedValue = absentValue; // act const act = () => new ScriptingDefinitionBuilder() .withEndCode(undefinedValue) .build(); // assert expect(act).to.throw(expectedError); - } + }); }); }); }); diff --git a/tests/unit/infrastructure/CodeRunner.spec.ts b/tests/unit/infrastructure/CodeRunner.spec.ts index 7e2bb7aa..68835074 100644 --- a/tests/unit/infrastructure/CodeRunner.spec.ts +++ b/tests/unit/infrastructure/CodeRunner.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { EnvironmentStub } from '@tests/unit/stubs/EnvironmentStub'; import { OperatingSystem } from '@/domain/OperatingSystem'; import { CodeRunner } from '@/infrastructure/CodeRunner'; +import { expectThrowsAsync } from '@tests/unit/shared/Assertions/ExpectThrowsAsync'; describe('CodeRunner', () => { describe('runCode', () => { @@ -116,6 +117,17 @@ describe('CodeRunner', () => { .filter((command) => expectedOrder.includes(command)); expect(expectedOrder).to.deep.equal(actualOrder); }); + it('throws with unsupported os', async () => { + // arrange + const unknownOs = OperatingSystem.Android; + const expectedError = `unsupported os: ${OperatingSystem[unknownOs]}`; + const context = new TestContext() + .withOs(unknownOs); + // act + const act = async () => { await context.runCode(); }; + // assert + expectThrowsAsync(act, expectedError); + }); }); }); diff --git a/tests/unit/infrastructure/InMemoryRepository.spec.ts b/tests/unit/infrastructure/InMemoryRepository.spec.ts index 39f27caa..3227873b 100644 --- a/tests/unit/infrastructure/InMemoryRepository.spec.ts +++ b/tests/unit/infrastructure/InMemoryRepository.spec.ts @@ -2,6 +2,7 @@ import 'mocha'; import { expect } from 'chai'; import { NumericEntityStub } from '@tests/unit/stubs/NumericEntityStub'; import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('InMemoryRepository', () => { describe('exists', () => { @@ -32,24 +33,40 @@ describe('InMemoryRepository', () => { // assert expect(actual).to.deep.equal(expected); }); - it('addItem adds', () => { - // arrange - const sut = new InMemoryRepository(); - const expected = { - length: 1, - item: new NumericEntityStub(1), - }; + describe('addItem', () => { + it('adds', () => { + // arrange + const sut = new InMemoryRepository(); + const expected = { + length: 1, + item: new NumericEntityStub(1), + }; - // act - sut.addItem(expected.item); - const actual = { - length: sut.length, - item: sut.getItems()[0], - }; + // act + sut.addItem(expected.item); + const actual = { + length: sut.length, + item: sut.getItems()[0], + }; - // assert - expect(actual.length).to.equal(expected.length); - expect(actual.item).to.deep.equal(expected.item); + // assert + expect(actual.length).to.equal(expected.length); + expect(actual.item).to.deep.equal(expected.item); + }); + describe('throws when item is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing item'; + const sut = new InMemoryRepository(); + const item = absentValue; + + // act + const act = () => sut.addItem(item); + + // assert + expect(act).to.throw(expectedError); + }); + }); }); it('removeItem removes', () => { // arrange @@ -100,9 +117,23 @@ describe('InMemoryRepository', () => { const actual = sut.getItems(); expect(actual).to.deep.equal(expected); }); + describe('throws when item is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing item'; + const sut = new InMemoryRepository(); + const item = absentValue; + + // act + const act = () => sut.addOrUpdateItem(item); + + // assert + expect(act).to.throw(expectedError); + }); + }); }); describe('getById', () => { - it('gets entity if it exists', () => { + it('returns entity if it exists', () => { // arrange const expected = new NumericEntityStub(1).withCustomProperty('bca'); const sut = new InMemoryRepository([ @@ -114,7 +145,7 @@ describe('InMemoryRepository', () => { // assert expect(actual).to.deep.equal(expected); }); - it('gets undefined if it does not exist', () => { + it('returns undefined if it does not exist', () => { // arrange const sut = new InMemoryRepository([]); // act diff --git a/tests/unit/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.spec.ts b/tests/unit/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.spec.ts index b5fecb07..eb6ea768 100644 --- a/tests/unit/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.spec.ts +++ b/tests/unit/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler.spec.ts @@ -3,18 +3,21 @@ import { expect } from 'chai'; import { SelectionType, SelectionTypeHandler } from '@/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler'; import { scrambledEqual } from '@/application/Common/Array'; import { RecommendationLevel } from '@/domain/RecommendationLevel'; +import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; import { SelectionStateTestScenario } from './SelectionStateTestScenario'; describe('SelectionTypeHandler', () => { describe('ctor', () => { - it('throws when state is undefined', () => { - // arrange - const expectedError = 'undefined state'; - const state = undefined; - // act - const sut = () => new SelectionTypeHandler(state); - // assert - expect(sut).to.throw(expectedError); + describe('throws when state is missing', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing state'; + const state = absentValue; + // act + const sut = () => new SelectionTypeHandler(state); + // assert + expect(sut).to.throw(expectedError); + }); }); }); describe('selectType', () => { diff --git a/tests/unit/presentation/components/Shared/Throttle.spec.ts b/tests/unit/presentation/components/Shared/Throttle.spec.ts index 67475fb1..ac2a9e49 100644 --- a/tests/unit/presentation/components/Shared/Throttle.spec.ts +++ b/tests/unit/presentation/components/Shared/Throttle.spec.ts @@ -3,33 +3,62 @@ import { expect } from 'chai'; import { throttle, ITimer, TimeoutType } from '@/presentation/components/Shared/Throttle'; import { EventSource } from '@/infrastructure/Events/EventSource'; import { IEventSubscription } from '@/infrastructure/Events/IEventSource'; +import { AbsentObjectTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('throttle', () => { - it('throws if callback is undefined', () => { - // arrange - const expectedError = 'undefined callback'; - const callback = undefined; - // act - const act = () => throttle(callback, 500); - // assert - expect(act).to.throw(expectedError); - }); - describe('throws if waitInMs is negative or zero', () => { - // arrange - const testCases = [ - { value: 0, expectedError: 'no delay to throttle' }, - { value: -2, expectedError: 'negative delay' }, - ]; - const noopCallback = () => { /* do nothing */ }; - for (const testCase of testCases) { - it(`"${testCase.value}" throws "${testCase.expectedError}"`, () => { + describe('validates parameters', () => { + describe('throws if callback is absent', () => { + itEachAbsentObjectValue((absentValue) => { + // arrange + const expectedError = 'missing callback'; + const callback = absentValue; // act - const waitInMs = testCase.value; - const act = () => throttle(noopCallback, waitInMs); + const act = () => throttle(callback, 500); // assert - expect(act).to.throw(testCase.expectedError); + expect(act).to.throw(expectedError); }); - } + }); + describe('throws if waitInMs is negative or zero', () => { + // arrange + const testCases = [ + { + name: 'given zero', + value: 0, + expectedError: 'missing delay', + }, + { + name: 'given negative', + value: -2, + expectedError: 'negative delay', + }, + ...AbsentObjectTestCases.map((testCase) => ({ + name: `when absent (given ${testCase.valueName})`, + value: testCase.absentValue, + expectedError: 'missing delay', + })), + ]; + const noopCallback = () => { /* do nothing */ }; + for (const testCase of testCases) { + it(`"${testCase.name}" throws "${testCase.expectedError}"`, () => { + // act + const waitInMs = testCase.value; + const act = () => throttle(noopCallback, waitInMs); + // assert + expect(act).to.throw(testCase.expectedError); + }); + } + }); + it('throws if timer is null', () => { + // arrange + const expectedError = 'missing timer'; + const timer = null; + const noopCallback = () => { /* do nothing */ }; + const waitInMs = 1; + // act + const act = () => throttle(noopCallback, waitInMs, timer); + // assert + expect(act).to.throw(expectedError); + }); }); it('should call the callback immediately', () => { // arrange diff --git a/tests/unit/shared/Assertions/ExpectThrowsAsync.ts b/tests/unit/shared/Assertions/ExpectThrowsAsync.ts new file mode 100644 index 00000000..2be98236 --- /dev/null +++ b/tests/unit/shared/Assertions/ExpectThrowsAsync.ts @@ -0,0 +1,17 @@ +import { expect } from 'chai'; + +export async function expectThrowsAsync( + method: () => Promise, + errorMessage: string, +) { + let error: Error; + try { + await method(); + } catch (err) { + error = err; + } + expect(error).to.be.an(Error.name); + if (errorMessage) { + expect(error.message).to.equal(errorMessage); + } +} diff --git a/tests/unit/shared/TestCases/AbsentTests.ts b/tests/unit/shared/TestCases/AbsentTests.ts new file mode 100644 index 00000000..5ee0da6f --- /dev/null +++ b/tests/unit/shared/TestCases/AbsentTests.ts @@ -0,0 +1,68 @@ +export function itEachAbsentStringValue(runner: (absentValue: string) => void): void { + itEachTestCase(AbsentStringTestCases, runner); +} + +export function itEachAbsentObjectValue(runner: (absentValue: AbsentObjectType) => void): void { + itEachTestCase(AbsentObjectTestCases, runner); +} + +export function itEachAbsentCollectionValue(runner: (absentValue: []) => void): void { + itEachTestCase(getAbsentCollectionTestCases(), runner); +} + +function itEachTestCase( + testCases: readonly IAbsentTestCase[], + runner: (absentValue: T) => void, +): void { + for (const testCase of testCases) { + it(`given "${testCase.valueName}"`, () => { + runner(testCase.absentValue); + }); + } +} + +export const AbsentObjectTestCases: readonly IAbsentTestCase[] = [ + { + valueName: 'undefined', + absentValue: undefined, + }, + { + valueName: 'null', + absentValue: null, + }, +]; + +export const AbsentStringTestCases: readonly IAbsentStringCase[] = [ + { + valueName: 'empty', + absentValue: '', + }, + ...AbsentObjectTestCases, +]; + +export function getAbsentCollectionTestCases(): readonly IAbsentCollectionCase[] { + return [ + ...AbsentObjectTestCases, + { + valueName: 'empty', + absentValue: new Array(), + }, + ]; +} + +type AbsentObjectType = undefined | null; + +interface IAbsentTestCase { + valueName: string; + absentValue: T; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface IAbsentStringCase extends IAbsentTestCase { + // Marker interface +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface IAbsentCollectionCase extends IAbsentTestCase { + // Marker interface +} diff --git a/tests/unit/stubs/PipeFactoryStub.ts b/tests/unit/stubs/PipeFactoryStub.ts index 4d62b265..0661923d 100644 --- a/tests/unit/stubs/PipeFactoryStub.ts +++ b/tests/unit/stubs/PipeFactoryStub.ts @@ -14,7 +14,7 @@ export class PipeFactoryStub implements IPipeFactory { public withPipe(pipe: IPipe) { if (!pipe) { - throw new Error('undefined pipe'); + throw new Error('missing pipe'); } this.pipes.push(pipe); return this;