From 7de7d5e6cb45adbf74f15a3040a9cd3456397008 Mon Sep 17 00:00:00 2001 From: Louis DEVIE Date: Sat, 14 Mar 2026 00:33:47 +0100 Subject: [PATCH] finished implementation of Base64BitFlagSets --- node/src/builders/base64.ts | 73 ++++++++++++++++++ node/src/builders/declarative.ts | 67 ++++++++-------- node/src/builders/index.ts | 1 + node/src/builders/syntax.ts | 27 +++---- node/src/definitions/base64.ts | 118 +++++++++++++++++++++++++++++ node/src/definitions/graph.ts | 30 ++++---- node/src/definitions/index.ts | 1 + node/src/flagsets/base64.ts | 81 +++++++++++++++----- node/src/flagsets/index.ts | 8 +- node/src/index.ts | 1 + node/tests/flagsets/base64.test.ts | 103 +++++++++++++++++++++++-- 11 files changed, 419 insertions(+), 91 deletions(-) create mode 100644 node/src/builders/base64.ts create mode 100644 node/src/definitions/base64.ts diff --git a/node/src/builders/base64.ts b/node/src/builders/base64.ts new file mode 100644 index 0000000..ccc209a --- /dev/null +++ b/node/src/builders/base64.ts @@ -0,0 +1,73 @@ +import { Base64BitFlagDefinitionFactory, FlagDefinition } from '~/definitions' +import { Base64BitFlagSet } from '~/flagsets' + +import { + applyDeclarations, + FlagWithOrdinal, + ListOfFlagsWithOrdinal, + NamedFlagWithOrdinal, +} from './declarative' +import { FlagSetBuilder } from './generic' +import { + DefineWithOrdinal, + RequireParentsOrDefineWithOrdinal, + WithOrdinal, + WithOrdinalOrCompose, +} from './syntax' +import { InvalidOperationError } from '~/errors' + +export class Base64BitFlagSetBuilder + implements + WithOrdinalOrCompose, + RequireParentsOrDefineWithOrdinal +{ + private readonly _underlying: FlagSetBuilder + + public constructor() { + this._underlying = new FlagSetBuilder() + } + + public define(): WithOrdinal + public define(alias: string): WithOrdinalOrCompose + public define(alias?: string): WithOrdinalOrCompose { + this._underlying.define(alias) + return this + } + + public compose(...flags: string[]): DefineWithOrdinal { + this._underlying.compose(flags) + return this + } + + public withValue(): never { + throw new InvalidOperationError('withValue') + } + + public withOrdinal( + ordinal: number, + ): RequireParentsOrDefineWithOrdinal { + this._underlying.withValue(ordinal) + return this + } + + public requires(...flags: string[]): DefineWithOrdinal { + this._underlying.requires(flags) + return this + } + + public getResult(): Base64BitFlagSet { + const graph = this._underlying.finish() + const factory = new Base64BitFlagDefinitionFactory() + return new Base64BitFlagSet(graph.intoDictionary(factory)) + } +} + +export function createBase64BitFlagSet(declarations: FlagWithOrdinal[]): Base64BitFlagSet +export function createBase64BitFlagSet( + declarations: Record, +): Base64BitFlagSet & Record> +export function createBase64BitFlagSet(declarations: ListOfFlagsWithOrdinal): Base64BitFlagSet { + const builder = new Base64BitFlagSetBuilder() + applyDeclarations(declarations, builder) + return builder.getResult() +} diff --git a/node/src/builders/declarative.ts b/node/src/builders/declarative.ts index 5c31622..1c168a7 100644 --- a/node/src/builders/declarative.ts +++ b/node/src/builders/declarative.ts @@ -1,15 +1,4 @@ -import type { DefineWithOrdinal, DefineWithValue, DefineWithValueOrOrdinal } from './syntax' -import { FlagsGraph, refByAlias } from '~/definitions' - -// Generic helper functions - -/** - * Copies the record keys into the 'as' property of the values and return an - * array containing those values. - */ -function toDeclarationArray(record: Record): (D & { as: string })[] { - return Object.keys(record).map((key) => ({ ...record[key], as: key })) -} +import type { DefineWithValueOrOrdinal } from './syntax' // Declarations for builders that supports only definitions by value @@ -23,17 +12,13 @@ export type ListOfFlagsWithValue = FlagWithValue[] | Record = +export type FlagWithOrdinal = | { ordinal: number; as?: string; requires?: string[] } | { compose: string[]; as: string } -export type NamedFlagWithOrdinal = - | { ordinal: number; requires?: string[] } - | { compose: string[] } +export type NamedFlagWithOrdinal = { ordinal: number; requires?: string[] } | { compose: string[] } -export type ListOfFlagsWithOrdinal = - | FlagWithOrdinal[] - | Record> +export type ListOfFlagsWithOrdinal = FlagWithOrdinal[] | Record // Declarations for builders that supports definitions by value and ordinal @@ -51,27 +36,43 @@ export type ListOfFlagsWithValueOrOrdinal = | FlagWithValueOrOrdinal[] | Record> +// Helper function + +export interface AnyBuilder { + define(alias: string | undefined): unknown + compose(...flags: string[]): unknown + withOrdinal(ordinal: number): unknown + withValue(value: F): unknown + requires(...flags: string[]): unknown +} + export function applyDeclarations( declarations: ListOfFlagsWithValueOrOrdinal, - builder: DefineWithValueOrOrdinal, + builder: AnyBuilder, ) { - const declarationsArray = Array.isArray(declarations) - ? declarations - : toDeclarationArray(declarations) + let declarationsArray: FlagWithValueOrOrdinal[] + if (Array.isArray(declarations)) { + declarationsArray = declarations + } else { + declarationsArray = Object.keys(declarations).map((key): FlagWithValueOrOrdinal => { + return { ...declarations[key], as: key } + }) + } for (const declaration of declarationsArray) { + builder.define(declaration.as) if ('compose' in declaration) { - builder.define(declaration.as).compose(...declaration.compose) - } else if ('ordinal' in declaration) { - builder - .define(declaration.as!) // see note above - .withOrdinal(declaration.ordinal) - .requires(...(declaration.requires ?? [])) + builder.compose(...declaration.compose) } else { - builder - .define(declaration.as!) // see note above - .withValue(declaration.value) - .requires(...(declaration.requires ?? [])) + if ('ordinal' in declaration) { + builder.withOrdinal(declaration.ordinal) + } else { + builder.withValue(declaration.value) + } + + if (declaration.requires !== undefined) { + builder.requires(...declaration.requires) + } } } } diff --git a/node/src/builders/index.ts b/node/src/builders/index.ts index 79bf65b..8de5cbd 100644 --- a/node/src/builders/index.ts +++ b/node/src/builders/index.ts @@ -1,4 +1,5 @@ export { ArrayFlagSetBuilder, createArrayFlagSet } from './array' +export { Base64BitFlagSetBuilder, createBase64BitFlagSet } from './base64' export { BigBitFlagSetBuilder, createBigBitFlagSet } from './bigint' export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection' export { BitFlagSetBuilder, createBitFlagSet } from './number' diff --git a/node/src/builders/syntax.ts b/node/src/builders/syntax.ts index 0a207f6..b2589f2 100644 --- a/node/src/builders/syntax.ts +++ b/node/src/builders/syntax.ts @@ -1,4 +1,4 @@ -import type { FlagsDictionary, FlagSet } from '~' +import type { FlagSet } from '~' export interface Root> { /** @@ -48,42 +48,42 @@ export interface RequireParentsOrDefineWithValue extends DefineWithValu // Syntax for builders that supports only definitions by ordinal -export interface DefineWithOrdinal> extends Root { +export interface DefineWithOrdinal> extends Root { /** * Define an anonymous flag. */ - define(): WithOrdinal + define(): WithOrdinal /** * Define a named flag. * @param alias The name of the flag. */ - define(alias: string): WithOrdinalOrCompose + define(alias: string): WithOrdinalOrCompose } -export interface WithOrdinal { +export interface WithOrdinal { /** * Set the value of this flag. * @param ordinal The number of the flag (starting at 1). A unique value * will be assigned based on this number. */ - withOrdinal(ordinal: number): RequireParentsOrDefineWithOrdinal + withOrdinal(ordinal: number): RequireParentsOrDefineWithOrdinal } -export interface WithOrdinalOrCompose extends WithOrdinal { +export interface WithOrdinalOrCompose extends WithOrdinal { /** * Define this flag as a composed flag. * @param flags The name of the flags in the group. */ - compose(...flags: string[]): DefineWithOrdinal + compose(...flags: string[]): DefineWithOrdinal } -export interface RequireParentsOrDefineWithOrdinal extends DefineWithOrdinal { +export interface RequireParentsOrDefineWithOrdinal extends DefineWithOrdinal { /** * Set the parents of this flag. * @param flags The names of the parent flags. */ - requires(...flags: string[]): DefineWithOrdinal + requires(...flags: string[]): DefineWithOrdinal } // Syntax for builders that supports definitions by value and ordinal @@ -124,11 +124,8 @@ export interface WithValueOrOrdinalOrCompose extends WithValueOrOrdinal compose(...flags: string[]): DefineWithValueOrOrdinal } -export interface RequireParentsOrDefineWithValueOrOrdinal extends DefineWithValueOrOrdinal< - F, - S, - R -> { +export interface RequireParentsOrDefineWithValueOrOrdinal + extends DefineWithValueOrOrdinal { /** * Set the parents of this flag. * @param flags The names of the parent flags. diff --git a/node/src/definitions/base64.ts b/node/src/definitions/base64.ts new file mode 100644 index 0000000..0cdd196 --- /dev/null +++ b/node/src/definitions/base64.ts @@ -0,0 +1,118 @@ +import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '~/base64' + +import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.' + +interface PrecomputedValues { + base: string + additive: string + subtractive: string +} + +function union(first: string, second: string): string { + const [shorter, longer] = first.length < second.length ? [first, second] : [second, first] + let result = '' + for (let i = 0; i < longer.length; i++) { + if (i < shorter.length) { + const value = decodeB64Byte(shorter[i]) | decodeB64Byte(longer[i]) + result += encodeB64Byte(value) + } else { + result += longer[i] + } + } + return normaliseB64String(result) +} + +export class Base64BitFlagDefinition implements FlagDefinition { + private readonly _baseValue: string + private readonly _additiveValue: string + private readonly _subtractiveValue: string + private readonly _alias: string | undefined + + public constructor(precomputedValues: PrecomputedValues, alias: string | undefined) { + this._baseValue = precomputedValues.base + this._additiveValue = precomputedValues.additive + this._subtractiveValue = precomputedValues.subtractive + this._alias = alias + } + + public get alias(): string | undefined { + return this._alias + } + + public get values(): string { + return this._baseValue + } + + public isIn(set: string): boolean { + let result = set.length >= this._additiveValue.length + for (let i = 0; i < this._additiveValue.length && result; i++) { + const value = decodeB64Byte(this._additiveValue[i]) + result = (decodeB64Byte(set[i]) & value) === value + } + return result + } + + public addTo(set: string): string { + return union(set, this._additiveValue) + } + + public removeFrom(set: string): string { + return set + } +} + +export class Base64BitFlagDefinitionFactory implements FlagDefinitionFactory { + public makeDefinitions( + sortedPartialDefinitions: PartialFlagDefinition[], + results: Map, FlagDefinition>, + ): void { + const precomputedValues = new Map, PrecomputedValues>() + + for (let i = 0; i < sortedPartialDefinitions.length; i++) { + const pfd = sortedPartialDefinitions[i] + + let stringValue = ZERO_STRING + if (pfd.value !== undefined) { + if (pfd.value < 1) { + throw new RangeError('Indices should be greater than or equal to 1.') + } + const indexFromZero = pfd.value - 1 + const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6) + const bigEnd = encodeB64Byte(1 << indexFromZero % 6) + stringValue = leadingBytes + bigEnd + } + + const values: PrecomputedValues = { + base: stringValue, + additive: stringValue, + subtractive: stringValue, + } + + for (const parentPfd of pfd.parents) { + const parentValues = precomputedValues.get(parentPfd) + if (parentValues !== undefined) { + values.additive = union(values.additive, parentValues.additive) + if (pfd.value === undefined) { + values.base = union(values.base, parentValues.base) + } + } + } + + precomputedValues.set(pfd, values) + } + + for (let i = sortedPartialDefinitions.length - 1; i >= 0; i--) { + const pfd = sortedPartialDefinitions[i] + const values = precomputedValues.get(pfd)! + + for (const childPfd of pfd.children) { + const childValues = precomputedValues.get(childPfd) + if (childValues !== undefined) { + values.subtractive = union(values.subtractive, childValues.subtractive) + } + } + + results.set(pfd, new Base64BitFlagDefinition(values, pfd.alias)) + } + } +} diff --git a/node/src/definitions/graph.ts b/node/src/definitions/graph.ts index 187fa32..b5f84d9 100644 --- a/node/src/definitions/graph.ts +++ b/node/src/definitions/graph.ts @@ -1,4 +1,4 @@ -import { FlagDefinition, FlagsDictionary, printFlagValue } from '~/definitions' +import { FlagDefinition, FlagsDictionary } from '~/definitions' import { InternalError } from '~/errors' export interface PartialFlagInit { @@ -14,7 +14,7 @@ export function refByAlias(refs: string[]): PartialFlagInit[] { export interface FlagDefinitionFactory { makeDefinitions( sortedPartialDefinitions: PartialFlagDefinition[], - results: Map, FlagDefinition>, + results: Map, FlagDefinition>, ): void } @@ -130,23 +130,27 @@ export class FlagsGraph { public intoDictionary(factory: FlagDefinitionFactory): FlagsDictionary { const sortedPartialDefinitions = this.sortedDefinitions() - const definitions = new Map, FlagDefinition>() + const definitions = new Map, FlagDefinition>() factory.makeDefinitions(sortedPartialDefinitions, definitions) - const aliasToDefinition = new Map>() + const aliasToDefinition = new Map>() for (const [alias, pfd] of this._aliasToDefinition.entries()) { const definition = definitions.get(pfd) if (definition === undefined) { - throw new InternalError(`factory didn't provide any definition for ${pfd}`) + throw new InternalError( + `factory didn't provide any definition for ${pfd.toString()}`, + ) } aliasToDefinition.set(alias, definition) } - const valueToDefinition = new Map>() + const valueToDefinition = new Map>() for (const [value, pfd] of this._valueToDefinition.entries()) { const definition = definitions.get(pfd) if (definition === undefined) { - throw new InternalError(`factory didn't provide any definition for ${pfd}`) + throw new InternalError( + `factory didn't provide any definition for ${pfd.toString()}`, + ) } valueToDefinition.set(value, definition) } @@ -156,22 +160,22 @@ export class FlagsGraph { } class BiMapFlagsDictionary implements FlagsDictionary { - private readonly _aliasToDefinition: Map> - private readonly _valueToDefinition: Map> + private readonly _aliasToDefinition: Map> + private readonly _valueToDefinition: Map> public constructor( - aliasToDefinition: Map>, - valueToDefinition: Map>, + aliasToDefinition: Map>, + valueToDefinition: Map>, ) { this._aliasToDefinition = aliasToDefinition this._valueToDefinition = valueToDefinition } - public findByAlias(alias: string): FlagDefinition | undefined { + public findByAlias(alias: string): FlagDefinition | undefined { return this._aliasToDefinition.get(alias) } - public findByValue(value: F): FlagDefinition | undefined { + public findByValue(value: F): FlagDefinition | undefined { return this._valueToDefinition.get(value) } } diff --git a/node/src/definitions/index.ts b/node/src/definitions/index.ts index 28e9494..9fc930f 100644 --- a/node/src/definitions/index.ts +++ b/node/src/definitions/index.ts @@ -1,3 +1,4 @@ +export { Base64BitFlagDefinition, Base64BitFlagDefinitionFactory } from './base64' export { BigBitFlagDefinition, BigBitFlagDefinitionFactory } from './bigint' export { FlagDefinition, FlagsDictionary, printFlagValue, valueToString } from './dictionary' export { diff --git a/node/src/flagsets/base64.ts b/node/src/flagsets/base64.ts index 3b351fe..c12a6a5 100644 --- a/node/src/flagsets/base64.ts +++ b/node/src/flagsets/base64.ts @@ -1,6 +1,18 @@ +import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '~/base64' +import { FlagDefinition, FlagsDictionary } from '~/definitions' +import { Base64BitflagIterator, EnumerateFlags, useIterator } from '~/enumeration' + import type { FlagSet } from '.' -import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '../base64' -import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumeration' + +function toBase64(value: number): string { + if (value < 1) { + throw new RangeError('Indices should be greater than or equal to 1.') + } + const indexFromZero = value - 1 + const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6) + const bigEnd = encodeB64Byte(1 << indexFromZero % 6) + return leadingBytes + bigEnd +} /** * Provides flags that are stored in strings using a little-endian base 64 @@ -11,22 +23,31 @@ import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumerati * instead if you need the data to be easily understandable by other systems. */ export class Base64BitFlagSet implements FlagSet { - /*protected wrapValue(value: number): string { - if (value < 1) { - throw new RangeError( - 'Indices should be greater than or equal to 1.' - ) - } - const indexFromZero = value - 1 - const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6) - const bigEnd = encodeByte(1 << indexFromZero % 6) - return leadingBytes + bigEnd - }*/ + private readonly _dictionary: FlagsDictionary + + public constructor(dictionary: FlagsDictionary) { + this._dictionary = dictionary + } public none(): string { return '' } + public of(...values: number[]): string { + return normaliseB64String( + values.reduce((set, value) => this.union(set, toBase64(value)), ZERO_STRING), + ) + } + + public named(...aliases: string[]): string { + return normaliseB64String( + aliases.reduce( + (set, alias) => this.union(set, this.getFlag(alias)?.values ?? ZERO_STRING), + ZERO_STRING, + ), + ) + } + public union(first: string, second: string): string { let result = '' @@ -107,19 +128,41 @@ export class Base64BitFlagSet implements FlagSet { return result } + public hasAny(flags: string, required: string): boolean { + return this.minimum(this.intersection(flags, required)) !== '' + } + + public hasAll(flags: string, required: string): boolean { + return this.isSuperset(flags, this.maximum(required)) + } + public enumerate(flags: string): EnumerateFlags { return useIterator(flags, Base64BitflagIterator) } - maximum(flags: string): string { - throw new Error('not implemented') + public maximum(flags: string): string { + let result = ZERO_STRING + for (const value of this.enumerate(flags)) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined) { + result = definition.addTo(result) + } + } + return normaliseB64String(result) } - minimum(flags: string): string { - throw new Error('not implemented') + public minimum(flags: string): string { + let result = ZERO_STRING + for (const value of this.enumerate(flags)) { + const definition = this._dictionary.findByValue(value) + if (definition !== undefined && definition.isIn(flags)) { + result = definition.addTo(result) + } + } + return normaliseB64String(result) } - public getFlag(alias: string): FlagDefinition | undefined { - return this._dictionary.lookUp(alias) + public getFlag(alias: string): FlagDefinition | undefined { + return this._dictionary.findByAlias(alias) } } diff --git a/node/src/flagsets/index.ts b/node/src/flagsets/index.ts index cd289b7..1f9db76 100644 --- a/node/src/flagsets/index.ts +++ b/node/src/flagsets/index.ts @@ -1,5 +1,5 @@ -import { EnumerateFlags } from '../enumeration' -import { FlagDefinition } from '../definitions' +import { FlagDefinition } from '~/definitions' +import { EnumerateFlags } from '~/enumeration' /** * Represents a group of flags of type `F` and the relationships between @@ -128,11 +128,11 @@ export interface FlagSet { * @returns The corresponding definition, or `undefined` if there is no flag * with this alias. */ - getFlag(alias: string): FlagDefinition | undefined + getFlag(alias: string): FlagDefinition | undefined } export { ArrayFlagSet } from './array' export { Base64BitFlagSet } from './base64' export { BigBitFlagSet } from './bigint' export { CollectionFlagSet } from './collection' -export { BitFlagSet, BitFlags } from './number' +export { BitFlags, BitFlagSet } from './number' diff --git a/node/src/index.ts b/node/src/index.ts index fac517c..ea83e21 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -3,6 +3,7 @@ export { createBigBitFlagSet, createBitFlagSet, createCollectionFlagSet, + createBase64BitFlagSet, } from './builders' export { FlagDefinition } from './definitions' export { InvalidBitFlagValueError } from './errors' diff --git a/node/tests/flagsets/base64.test.ts b/node/tests/flagsets/base64.test.ts index 7cb04cf..a90b32b 100644 --- a/node/tests/flagsets/base64.test.ts +++ b/node/tests/flagsets/base64.test.ts @@ -1,15 +1,40 @@ -import { Base64BitFlagSet } from '~' import { describe, expect, test } from 'vitest' +import { Base64BitFlagSet, createBase64BitFlagSet } from '~' + describe(Base64BitFlagSet, () => { test('none', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect(flags.none()).toEqual('') }) + test('of', () => { + const flags = createBase64BitFlagSet([]) + + expect(flags.of()).toEqual('') + expect(flags.of(1)).toEqual('B') + expect(flags.of(2, 3)).toEqual('G') + }) + + test('named', () => { + const flags = createBase64BitFlagSet([ + { ordinal: 1, as: 'A' }, + { ordinal: 2, as: 'B' }, + { ordinal: 3, as: 'C' }, + { ordinal: 4, as: 'D' }, + { compose: ['A', 'B'], as: 'AB' }, + { compose: ['A', 'C'], as: 'AC' }, + ]) + + expect(flags.named()).toEqual('') + expect(flags.named('A')).toEqual('B') + expect(flags.named('AB', 'D')).toEqual('L') + expect(flags.named('AB', 'AC', 'B')).toEqual('H') + }) + test('union', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect(flags.union('', '')).toEqual('') expect(flags.union('A', 'A')).toEqual('') @@ -20,7 +45,7 @@ describe(Base64BitFlagSet, () => { }) test('difference', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect(flags.difference('', '')).toEqual('') expect(flags.difference('A', 'A')).toEqual('') @@ -31,7 +56,7 @@ describe(Base64BitFlagSet, () => { }) test('intersection', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect(flags.intersection('', '')).toEqual('') expect(flags.intersection('A', 'A')).toEqual('') @@ -43,7 +68,7 @@ describe(Base64BitFlagSet, () => { }) test('isSuperset', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect(flags.isSuperset('A', 'A')).toBe(true) expect(flags.isSuperset('D', 'A')).toBe(true) @@ -53,8 +78,40 @@ describe(Base64BitFlagSet, () => { expect(flags.isSuperset('I', 'E')).toBe(false) }) + test('hasAny', () => { + const flags = createBase64BitFlagSet([ + { ordinal: 1, as: 'A' }, + { ordinal: 2, as: 'B', requires: ['A'] }, + { ordinal: 3, as: 'C', requires: ['A'] }, + { ordinal: 4, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAny('P', 'A')).toBe(false) + expect(flags.hasAny('A', 'A')).toBe(false) + expect(flags.hasAny('H', 'B')).toBe(true) + expect(flags.hasAny('B', 'H')).toBe(true) + expect(flags.hasAny('F', 'M')).toBe(false) + expect(flags.hasAny('C', 'C')).toBe(false) + }) + + test('hasAll', () => { + const flags = createBase64BitFlagSet([ + { ordinal: 1, as: 'A' }, + { ordinal: 2, as: 'B', requires: ['A'] }, + { ordinal: 3, as: 'C', requires: ['A'] }, + { ordinal: 4, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAll('P', 'A')).toBe(true) + expect(flags.hasAll('A', 'A')).toBe(true) + expect(flags.hasAll('H', 'C')).toBe(true) + expect(flags.hasAll('G', 'C')).toBe(false) + expect(flags.hasAll('B', 'H')).toBe(false) + expect(flags.hasAll('F', 'M')).toBe(false) + }) + test('enumerate', () => { - const flags = new Base64BitFlagSet() + const flags = createBase64BitFlagSet([]) expect([...flags.enumerate('A')]).toEqual([]) expect([...flags.enumerate('B')]).toEqual([1]) @@ -64,4 +121,36 @@ describe(Base64BitFlagSet, () => { expect([...flags.enumerate('kB')]).toEqual([3, 6, 7]) expect([...flags.enumerate('AAB')]).toEqual([13]) }) + + test('maximum', () => { + const flags = createBase64BitFlagSet([ + { ordinal: 1, as: 'A' }, + { ordinal: 2, as: 'B', requires: ['A'] }, + { ordinal: 3, as: 'C', requires: ['A'] }, + { ordinal: 4, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.maximum('A')).toEqual('') + expect(flags.maximum('B')).toEqual('B') + expect(flags.maximum('C')).toEqual('D') + expect(flags.maximum('D')).toEqual('D') + expect(flags.maximum('E')).toEqual('F') + expect(flags.maximum('I')).toEqual('P') + }) + + test('minimum', () => { + const flags = createBase64BitFlagSet([ + { ordinal: 1, as: 'A' }, + { ordinal: 2, as: 'B', requires: ['A'] }, + { ordinal: 3, as: 'C', requires: ['A'] }, + { ordinal: 4, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.minimum('A')).toEqual('') + expect(flags.minimum('B')).toEqual('B') + expect(flags.minimum('C')).toEqual('') + expect(flags.minimum('D')).toEqual('D') + expect(flags.minimum('E')).toEqual('') + expect(flags.minimum('N')).toEqual('F') + }) })