Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
201 changes: 201 additions & 0 deletions site/src/pages/TemplateBuilder/wizardState.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { describe, expect, it } from "vitest";
import type { TemplateBuilderBase } from "#/api/typesGenerated";
import {
baseCustomizationDefaults,
initialWizardState,
initWizardState,
moduleHasConfigurableVars,
type SelectedBaseMeta,
type TemplateBuilderWizardState,
toComposeRequest,
toCreateTemplateRequest,
toSelectedBaseMeta,
type WizardAction,
wizardReducer,
} from "./wizardState";
Expand Down Expand Up @@ -34,6 +39,90 @@ describe("wizardReducer", () => {
expect(state.selectedBase?.name).toBe("Docker");
});

it("seeds customization defaults from the selected base", () => {
const state = reduce([
{
type: "SET_BASE",
base: {
id: "docker",
name: "Docker Containers",
description: "Run workspaces as Docker containers",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
},
},
]);
expect(state.name).toBe("docker");
expect(state.displayName).toBe("Docker Containers");
expect(state.description).toBe("Run workspaces as Docker containers");
expect(state.icon).toBe("/icon/docker.png");
});

it("re-seeds customization defaults when the base changes", () => {
const state = reduce([
{
type: "SET_BASE",
base: {
id: "docker",
name: "Docker Containers",
description: "Docker",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
},
},
{
type: "SET_BASE",
base: {
id: "aws-linux",
name: "AWS Linux",
description: "AWS EC2 Linux",
iconUrl: "/icon/aws.svg",
hasParameters: false,
hasPrerequisites: false,
},
},
]);
expect(state.name).toBe("aws-linux");
expect(state.displayName).toBe("AWS Linux");
expect(state.description).toBe("AWS EC2 Linux");
expect(state.icon).toBe("/icon/aws.svg");
});

it("preserves customization edits when same base is re-selected", () => {
const state = reduce([
{
type: "SET_BASE",
base: {
id: "docker",
name: "Docker Containers",
description: "Docker",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
},
},
{
type: "SET_CUSTOMIZATION",
field: "displayName",
value: "My Custom Name",
},
{
type: "SET_BASE",
base: {
id: "docker",
name: "Docker Containers",
description: "Docker",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
},
},
]);
expect(state.displayName).toBe("My Custom Name");
});

it("clears base variable values when base changes", () => {
const state = reduce([
{
Expand Down Expand Up @@ -270,6 +359,38 @@ describe("wizardReducer", () => {
});
});

describe("RESET_CUSTOMIZATIONS", () => {
it("clears org and provisioner state but keeps base-derived fields", () => {
const state = reduce([
{
type: "SET_BASE",
base: {
id: "docker",
name: "Docker Containers",
description: "Docker",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
},
},
{
type: "SET_CUSTOMIZATION",
field: "organizationId",
value: "org-123",
},
{ type: "SET_HAS_PROVISIONERS", value: true },
{ type: "RESET_CUSTOMIZATIONS" },
]);
expect(state.organizationId).toBeUndefined();
expect(state.hasProvisioners).toBeUndefined();
// Base-derived fields survive so re-entering the step stays filled.
expect(state.name).toBe("docker");
expect(state.displayName).toBe("Docker Containers");
expect(state.description).toBe("Docker");
expect(state.icon).toBe("/icon/docker.png");
});
});

describe("RESET", () => {
it("returns to initial state", () => {
const state = reduce([
Expand Down Expand Up @@ -432,3 +553,83 @@ describe("toCreateTemplateRequest", () => {
expect(request.icon).toBeUndefined();
});
});

describe("toSelectedBaseMeta", () => {
const makeBase = (
overrides: Partial<TemplateBuilderBase> = {},
): TemplateBuilderBase => ({
id: "docker",
name: "Docker Containers",
description: "Run workspaces as Docker containers",
icon: "/icon/docker.png",
os: "linux",
variables: [],
prerequisites: "",
...overrides,
});

it("carries description and icon through to the UI metadata", () => {
const meta = toSelectedBaseMeta(makeBase());
expect(meta.description).toBe("Run workspaces as Docker containers");
expect(meta.iconUrl).toBe("/icon/docker.png");
expect(meta.name).toBe("Docker Containers");
expect(meta.id).toBe("docker");
});
});

describe("baseCustomizationDefaults", () => {
it("maps base metadata to editable customization defaults", () => {
const base: SelectedBaseMeta = {
id: "aws-linux",
name: "AWS Linux",
description: "AWS EC2 Linux",
iconUrl: "/icon/aws.svg",
hasParameters: false,
hasPrerequisites: false,
};
expect(baseCustomizationDefaults(base)).toEqual({
name: "aws-linux",
displayName: "AWS Linux",
description: "AWS EC2 Linux",
icon: "/icon/aws.svg",
});
});

it("falls back to empty strings for missing description and icon", () => {
const base: SelectedBaseMeta = {
id: "scratch",
name: "Scratch",
hasParameters: false,
hasPrerequisites: false,
};
expect(baseCustomizationDefaults(base)).toEqual({
name: "scratch",
displayName: "Scratch",
description: "",
icon: "",
});
});
});

describe("initWizardState", () => {
it("returns the initial state without a preselected base", () => {
expect(initWizardState()).toEqual(initialWizardState);
});

it("seeds base and customization defaults from a preselected base", () => {
const state = initWizardState({
id: "docker",
name: "Docker Containers",
description: "Docker",
iconUrl: "/icon/docker.png",
hasParameters: false,
hasPrerequisites: false,
});
expect(state.baseTemplateId).toBe("docker");
expect(state.selectedBase?.id).toBe("docker");
expect(state.name).toBe("docker");
expect(state.displayName).toBe("Docker Containers");
expect(state.description).toBe("Docker");
expect(state.icon).toBe("/icon/docker.png");
});
});
37 changes: 31 additions & 6 deletions site/src/pages/TemplateBuilder/wizardState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
export type SelectedBaseMeta = {
id: string;
name: string;
description?: string;
iconUrl?: string;
os?: string;
hasParameters: boolean;
Expand All @@ -28,6 +29,7 @@ export function toSelectedBaseMeta(
return {
id: base.id,
name: base.name,
description: base.description,
iconUrl: base.icon,
os: base.os,
hasParameters:
Expand All @@ -36,6 +38,24 @@ export function toSelectedBaseMeta(
};
}

/**
* Derives editable customization defaults from the selected base template.
* Empty base values fall through to the fields' existing placeholders.
*/
export function baseCustomizationDefaults(base: SelectedBaseMeta): {
name: string;
displayName: string;
description: string;
icon: string;
} {
return {
name: base.id,
displayName: base.name,
description: base.description ?? "",
icon: base.iconUrl ?? "",
};
}

/**
* UI-only metadata for a selected module.
* Kept separate from the API request payload.
Expand Down Expand Up @@ -88,6 +108,7 @@ export function initWizardState(
...initialWizardState,
baseTemplateId: preselectedBase.id,
selectedBase: preselectedBase,
...baseCustomizationDefaults(preselectedBase),
};
}

Expand Down Expand Up @@ -120,12 +141,17 @@ export function wizardReducer(
switch (action.type) {
case "SET_BASE": {
const baseChanged = state.baseTemplateId !== action.base.id;
if (!baseChanged) {
return { ...state, selectedBase: action.base };
}
// Changing the base clears base variable values and re-seeds the
// customization fields with defaults derived from the new base.
return {
...state,
baseTemplateId: action.base.id,
selectedBase: action.base,
// Clear base variable values when base changes.
baseVariableValues: baseChanged ? {} : state.baseVariableValues,
baseVariableValues: {},
...baseCustomizationDefaults(action.base),
};
}
case "SET_BASE_VARIABLES":
Expand Down Expand Up @@ -168,14 +194,13 @@ export function wizardReducer(
hasProvisioners: action.value,
};
case "RESET_CUSTOMIZATIONS":
// Reset only organization and provisioner detection so re-entering the
// step re-runs org auto-select cleanly. The base-derived fields are
// left intact (they are re-seeded by SET_BASE when the base changes).
return {
...state,
organizationId: undefined,
hasProvisioners: undefined,
name: "",
displayName: "",
description: "",
icon: "",
};
case "RESET":
return initialWizardState;
Expand Down
Loading