Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding): dont throw error for unknown presets #140

Merged
merged 3 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 87 additions & 48 deletions src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import type {
PresetField,
ProgressState,
ReachElementParams,
CommonPreset,
EventsMap,
EventTypes,
PresetStatus,
ResolvedOptions,
UserPreset,
} from './types';
import {CommonPreset, EventsMap, EventTypes, PresetStatus, ResolvedOptions} from './types';
import {HintStore} from './hints/hintStore';
import {createLogger, Logger} from './logger';
import {createDebounceHandler} from './debounce';
Expand Down Expand Up @@ -138,7 +143,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}, 100);

this.saveProgressState = createDebounceHandler(() => {
this.assertProgressLoaded();
this.progressLoadedGuard();

this.options.onSave.progress(this.state.progress);
}, 100);
Expand Down Expand Up @@ -263,7 +268,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

await this.ensureRunning();
this.assertProgressLoaded();
this.progressLoadedGuard();

if (this.hintStore.state.hint?.step.slug === stepSlug && this.hintStore.state.open) {
this.logger.debug('Updating hint anchor', preset, stepSlug);
Expand Down Expand Up @@ -362,27 +367,37 @@ export class Controller<HintParams, Presets extends string, Steps extends string

const userExistedPresetSlugs = allUserPresetSlugs.filter(this.isVisiblePreset) as Presets[];

return userExistedPresetSlugs.map((presetSlug) => {
let status: PresetStatus = 'unPassed';
return userExistedPresetSlugs
.map((presetSlug) => {
let status: PresetStatus = 'unPassed';
let slug: Presets | undefined;

const slug = this.resolvePresetSlug(presetSlug);
try {
slug = this.resolvePresetSlug(presetSlug);
} catch (e) {
status = 'unPassed';
}

if (this.state.base.activePresets.includes(slug)) {
status = 'inProgress';
} else if (this.state.progress?.finishedPresets.includes(slug)) {
status = 'finished';
}
if (!slug) {
status = 'unPassed';
} else if (this.state.base.activePresets.includes(slug)) {
status = 'inProgress';
} else if (this.state.progress?.finishedPresets.includes(slug)) {
status = 'finished';
}

return {
slug: presetSlug,
name: (this.options.config.presets[presetSlug] as CommonPreset<HintParams, Steps>)
.name,
description: (
this.options.config.presets[presetSlug] as CommonPreset<HintParams, Steps>
).description,
status,
};
});
return {
slug: presetSlug,
name: (
this.options.config.presets[presetSlug] as CommonPreset<HintParams, Steps>
).name,
description: (
this.options.config.presets[presetSlug] as CommonPreset<HintParams, Steps>
).description,
status,
};
})
.filter((userPreset) => Boolean(userPreset)) as UserPreset<Presets>[];
}

addPreset = async (presetArg: string | string[]) => {
Expand Down Expand Up @@ -410,21 +425,23 @@ export class Controller<HintParams, Presets extends string, Steps extends string

if (this.state.base.suggestedPresets.includes(preset)) {
this.logger.debug('Preset has already been suggested', preset);
return;
return false;
}

const allowRun = await this.events.emit('beforeSuggestPreset', {preset});

if (!allowRun) {
this.logger.debug('Preset suggestion cancelled', preset);
return;
return false;
}

await this.runPreset(preset);
return this.runPreset(preset);
};

runPreset = async (presetToRunSlug: string) => {
this.ensurePresetExists(presetToRunSlug);
if (!this.presetExistsGuard(presetToRunSlug)) {
return false;
}

const presetToRun = this.options.config.presets[presetToRunSlug];
const presetSlug = (
Expand All @@ -433,7 +450,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string

this.logger.debug('Running preset', presetSlug);

await this.options.config.presets[presetToRunSlug].hooks?.onBeforeStart?.();
await presetToRun.hooks?.onBeforeStart?.();
if (presetSlug !== presetToRunSlug) {
await this.options.config.presets[presetSlug].hooks?.onBeforeStart?.();
}
Expand All @@ -445,7 +462,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

if (this.state.base.activePresets.includes(presetSlug)) {
return;
return false;
}

this.state.base.activePresets.push(presetSlug);
Expand All @@ -465,7 +482,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
});

this.events.emit('runPreset', {preset: presetSlug});
this.options.config.presets[presetToRunSlug].hooks?.onStart?.();
presetToRun.hooks?.onStart?.();
if (presetSlug !== presetToRunSlug) {
this.options.config.presets[presetSlug].hooks?.onStart?.();
}
Expand All @@ -474,11 +491,17 @@ export class Controller<HintParams, Presets extends string, Steps extends string

await this.updateBaseState();
this.logger.debug('Preset ran', presetSlug);

return true;
};

finishPreset = async (presetToFinish: Presets, shouldSave = true) => {
// take normal or find internal
const presetSlug = this.resolvePresetSlug(presetToFinish);
if (!presetSlug) {
return false;
}

this.logger.debug('Preset finished', presetToFinish);

this.events.emit('finishPreset', {preset: presetSlug});
Expand All @@ -490,7 +513,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

await this.ensureRunning();
this.assertProgressLoaded();
this.progressLoadedGuard();

this.state.base.activePresets = this.state.base.activePresets.filter(
(activePresetSlug) => activePresetSlug !== presetSlug,
Expand All @@ -504,6 +527,8 @@ export class Controller<HintParams, Presets extends string, Steps extends string
await this.updateBaseState();
await this.updateProgress();
}

return true;
};

resetPresetProgress = async (
Expand All @@ -512,11 +537,13 @@ export class Controller<HintParams, Presets extends string, Steps extends string
) => {
this.logger.debug('Reset progress for', presetArg);
await this.ensureRunning();
this.assertProgressLoaded();
this.progressLoadedGuard();

const presets = this.filterExistedPresets(
Array.isArray(presetArg) ? presetArg : [presetArg],
).map((preset) => this.resolvePresetSlug(preset));
)
.map((preset) => this.resolvePresetSlug(preset))
.filter((preset) => Boolean(preset)) as Presets[];

this.state.progress.finishedPresets = this.state.progress.finishedPresets.filter(
(preset) => !presets.includes(preset as Presets),
Expand Down Expand Up @@ -613,7 +640,10 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}
}

private resolvePresetSlug = (presetSlug: Presets) => {
private resolvePresetSlug = (presetSlug: string) => {
if (!this.presetExistsGuard(presetSlug)) {
return undefined;
}
const preset = this.options.config.presets[presetSlug];

return preset.type === 'combined' ? this.findInternalPreset(presetSlug) : presetSlug;
Expand All @@ -623,16 +653,16 @@ export class Controller<HintParams, Presets extends string, Steps extends string
const preset = this.options.config.presets[presetSlug];

if (preset.type !== 'combined') {
throw new Error('not internal preset');
return undefined;
}

const activeInternalPreset = this.state.base.activePresets.find((activePreset) =>
preset.internalPresets.includes(activePreset),
) as Presets;
) as Presets | undefined;

const finishedInternalPreset = this.state.progress?.finishedPresets.find((finishedPreset) =>
preset.internalPresets.includes(finishedPreset),
) as Presets;
) as Presets | undefined;

return activeInternalPreset || finishedInternalPreset;
};
Expand All @@ -643,12 +673,12 @@ export class Controller<HintParams, Presets extends string, Steps extends string
};

private isVisiblePreset = (presetSlug: string) => {
const preset = this.options.config.presets[presetSlug as Presets];

if (!preset) {
if (!this.checkPresetExists(presetSlug)) {
return false;
}

const preset = this.options.config.presets[presetSlug as Presets];

const isInternal = preset.type === 'internal';
if (isInternal) {
return false;
Expand All @@ -665,7 +695,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
};

private findNextStepForPreset(presetSlug: Presets) {
this.assertProgressLoaded();
this.progressLoadedGuard();

const preset = this.options.config.presets[presetSlug as Presets];

Expand Down Expand Up @@ -731,7 +761,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
private async savePassedStepData(preset: Presets, step: Steps, callback?: () => void) {
this.logger.debug('Save passed step data', preset, step);

this.assertProgressLoaded();
this.progressLoadedGuard();

const passedSteps = this.state.progress.presetPassedSteps[preset] ?? [];

Expand All @@ -751,7 +781,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

private async checkAndProcessPresetFinish(presetSlug: Presets) {
this.assertProgressLoaded();
this.progressLoadedGuard();
const preset = this.options.config.presets[presetSlug];

if (!preset || preset.type === 'combined') {
Expand All @@ -770,7 +800,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

private async updateProgress() {
this.assertProgressLoaded();
this.progressLoadedGuard();
this.logger.debug('Update progress data', this.state.progress);

this.emitStateChange();
Expand All @@ -786,16 +816,25 @@ export class Controller<HintParams, Presets extends string, Steps extends string
await this.saveBaseState();
}

private ensurePresetExists(preset: string): asserts preset is Presets {
// @ts-ignore
if (!this.options.config.presets[preset]) {
private presetExistsGuard(preset: string): preset is Presets {
if (!this.checkPresetExists(preset)) {
this.logger.error('No preset in config', preset);

throw new Error('No preset in config');
if (!this.options.ignoreUnknownPresets) {
throw new Error('No preset in config');
}

return false;
}

return true;
}

private checkPresetExists(preset: string) {
return preset in this.options.config.presets;
}

private assertProgressLoaded(): asserts this is this & {
private progressLoadedGuard(): asserts this is this & {
state: {base: BaseState; progress: ProgressState};
} {
if (!this.state.progress) {
Expand Down Expand Up @@ -836,7 +875,7 @@ export class Controller<HintParams, Presets extends string, Steps extends string
}

private async goPrevStep(presetSlug: Presets) {
this.assertProgressLoaded();
this.progressLoadedGuard();

const preset = this.options.config.presets[presetSlug as Presets];

Expand Down
42 changes: 42 additions & 0 deletions src/tests/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,48 @@ describe('wrong data', function () {

expect(options.logger.logger.error).not.toHaveBeenCalled();
});

describe('ignoreUnknownPresets=true', () => {
const prepareOptions = () => {
const options = getOptions();
options.ignoreUnknownPresets = true;

return options;
};
let options = prepareOptions();

beforeEach(() => {
options = prepareOptions();
});

it('run preset -> nothing', async function () {
const controller = new Controller(options);

const result = await controller.runPreset('createQueue123');

expect(result).toBe(false);
expect(options.onSave.state).not.toHaveBeenCalled();
expect(options.onSave.progress).not.toHaveBeenCalled();
});

it('suggest not existed preset -> false', async function () {
const controller = new Controller(options);

const result = await controller.suggestPresetOnce('unknownPreset');

expect(result).toBe(false);
expect(options.logger.logger.error).toHaveBeenCalled();
});

it('run not existed preset -> return false', async function () {
const controller = new Controller(options);

const result = await controller.runPreset('unknownPreset');

expect(result).toBe(false);
expect(options.logger.logger.error).toHaveBeenCalled();
});
});
});

it('resetToDefaultState -> hidden and empty ', async function () {
Expand Down
1 change: 1 addition & 0 deletions src/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const getOptions = (
},
showHint: jest.fn(),
debugMode: false,
ignoreUnknownPresets: false,
logger: {
level: 'error' as const,
logger: {
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export type CombinedPreset<InternalPresets extends string> = {
pickPreset: () => InternalPresets | Promise<InternalPresets>;
};

export type UserPreset<Presets extends string> = {
slug: Presets;
name: string;
description: string;
status: PresetStatus;
};

export type PresetFunctions = {
goNextStep: VoidFn;
goPrevStep: VoidFn;
Expand Down Expand Up @@ -133,6 +140,7 @@ export type InitOptions<HintParams, Presets extends string, Steps extends string
};
showHint?: (params: ShowHintParams<HintParams, Presets, Steps>) => void;
logger?: LoggerOptions;
ignoreUnknownPresets?: boolean;
debugMode?: boolean;
plugins?: OnboardingPlugin[];
hooks?: {
Expand Down
Loading