From 7abbe0dafd2f4119b9b783de0ab68fe6ee122504 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:00:36 -0500 Subject: [PATCH] State definitions tests (#7173) * Add StateDefinition Tests and Rules * Update Comments --- .../platform/state/state-definitions.spec.ts | 53 +++++++++++++++++++ .../src/platform/state/state-definitions.ts | 17 ++++++ 2 files changed, 70 insertions(+) create mode 100644 libs/common/src/platform/state/state-definitions.spec.ts diff --git a/libs/common/src/platform/state/state-definitions.spec.ts b/libs/common/src/platform/state/state-definitions.spec.ts new file mode 100644 index 0000000000..7caa22cd74 --- /dev/null +++ b/libs/common/src/platform/state/state-definitions.spec.ts @@ -0,0 +1,53 @@ +import { StateDefinition } from "./state-definition"; +import * as stateDefinitionsRecord from "./state-definitions"; + +describe("state definitions", () => { + const trackedNames: [string, string][] = []; + + test.each(Object.entries(stateDefinitionsRecord))( + "that export %s follows all rules", + (exportName, stateDefinition) => { + // All exports from state-definitions are expected to be StateDefinition's + if (!(stateDefinition instanceof StateDefinition)) { + throw new Error(`export ${exportName} is expected to be a StateDefinition`); + } + + const fullName = `${stateDefinition.name}_${stateDefinition.storageLocation}`; + + const exactConflictingExport = trackedNames.find( + ([_, trackedName]) => trackedName === fullName, + ); + if (exactConflictingExport !== undefined) { + const [conflictingExportName] = exactConflictingExport; + throw new Error( + `The export '${exportName}' has a conflicting state name and storage location with export ` + + `'${conflictingExportName}' please ensure that you choose a unique name and location.`, + ); + } + + const roughConflictingExport = trackedNames.find( + ([_, trackedName]) => trackedName.toLowerCase() === fullName.toLowerCase(), + ); + if (roughConflictingExport !== undefined) { + const [conflictingExportName] = roughConflictingExport; + throw new Error( + `The export '${exportName}' differs its state name and storage location ` + + `only by casing with export '${conflictingExportName}' please ensure it differs by more than casing.`, + ); + } + + const name = stateDefinition.name; + + expect(name).not.toBeUndefined(); // undefined in an invalid name + expect(name).not.toBeNull(); // null is in invalid name + expect(name.length).toBeGreaterThan(3); // A 3 characters or less name is not descriptive enough + expect(name[0]).toEqual(name[0].toLowerCase()); // First character should be lower case since camelCase is required + expect(name).not.toContain(" "); // There should be no spaces in a state name + expect(name).not.toContain("_"); // We should not be doing snake_case for state name + + // NOTE: We could expect some details about the export name as well + + trackedNames.push([exportName, fullName]); + }, + ); +}); diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 4ec1a6a87f..d4943ef35f 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -1,3 +1,20 @@ import { StateDefinition } from "./state-definition"; +/** + * `StateDefinition`s comes with some rules, to facilitate a quick review from + * platform of this file, ensure you follow these rules, the ones marked with (tested) + * have unit tests that you can run locally. + * + * 1. (tested) Names should not be null or undefined + * 2. (tested) Name and storage location should be unique + * 3. (tested) Name and storage location can't differ from another export by only casing + * 4. (tested) Name should be longer than 3 characters. It should be descriptive, but brief. + * 5. (tested) Name should not contain spaces or underscores + * 6. Name should be human readable + * 7. Name should be in camelCase format (unit tests ensure the first character is lowercase) + * 8. Teams should only use state definitions they have created + * 9. StateDefinitions should only be used for keys relating to the state name they chose + * + */ + export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");