From cef726c5196bbcbb097cb4976619324a32906012 Mon Sep 17 00:00:00 2001 From: Template Bot Date: Mon, 9 Feb 2026 04:30:31 +0000 Subject: [PATCH] Apply template update: switch to vitest (conflicts) Source: https://github.com/mockdeep/Rails-Template/pull/1328 This cherry-pick had conflicts that need manual resolution. Search for <<<<<<< in the changed files. --- spec/javascript/channels/consumer_spec.ts | 6 ++ .../controllers/dialog_controller_spec.ts | 10 +++ .../controllers/hotkeys_controller_spec.ts | 74 +++++++++++++++++++ spec/javascript/helpers/assert_spec.ts | 33 +++++++++ spec/javascript/support/stimulus.ts | 46 ++++++++++++ spec/javascript/vitest-globals.d.ts | 1 + tsconfig.json | 1 + 7 files changed, 171 insertions(+) create mode 100644 spec/javascript/channels/consumer_spec.ts create mode 100644 spec/javascript/controllers/dialog_controller_spec.ts create mode 100644 spec/javascript/controllers/hotkeys_controller_spec.ts create mode 100644 spec/javascript/helpers/assert_spec.ts create mode 100644 spec/javascript/support/stimulus.ts create mode 100644 spec/javascript/vitest-globals.d.ts diff --git a/spec/javascript/channels/consumer_spec.ts b/spec/javascript/channels/consumer_spec.ts new file mode 100644 index 000000000..ccfee3749 --- /dev/null +++ b/spec/javascript/channels/consumer_spec.ts @@ -0,0 +1,6 @@ +import {expect, it} from "vitest"; +import consumer from "channels/consumer"; + +it("is defined", () => { + expect(consumer.url).toBe("ws://test.host/cable"); +}); diff --git a/spec/javascript/controllers/dialog_controller_spec.ts b/spec/javascript/controllers/dialog_controller_spec.ts new file mode 100644 index 000000000..35954f6ad --- /dev/null +++ b/spec/javascript/controllers/dialog_controller_spec.ts @@ -0,0 +1,10 @@ +import {expect, it} from "vitest"; +import {bootStimulus} from "spec/javascript/support/stimulus"; +import DialogController from "controllers/dialog_controller"; + +it("updates the text content of its element", async () => { + document.body.innerHTML = "
"; + await bootStimulus("dialog", DialogController); + + expect(document.body.textContent).toBe("Hello World!"); +}); diff --git a/spec/javascript/controllers/hotkeys_controller_spec.ts b/spec/javascript/controllers/hotkeys_controller_spec.ts new file mode 100644 index 000000000..c4654def5 --- /dev/null +++ b/spec/javascript/controllers/hotkeys_controller_spec.ts @@ -0,0 +1,74 @@ +import {describe, expect, it, vi} from "vitest"; +import {bootStimulus, getController} from "spec/javascript/support/stimulus"; +import HotkeysController from "controllers/hotkeys_controller"; +import {assert} from "javascript/helpers"; + +function setupDOM(): void { + document.body.innerHTML = ` +
+ +
+ `; +} + +async function setupController(): Promise { + setupDOM(); + + await bootStimulus("hotkeys", HotkeysController); +} + +function element(): HTMLElement { + const selector = "[data-controller='hotkeys']"; + + return assert(document.querySelector(selector)); +} + +function controller(): HotkeysController { + return getController(element(), "hotkeys", HotkeysController); +} + +function button(): HTMLButtonElement { + const selector = "button[data-hotkeys-target='click']"; + + return assert(document.querySelector(selector)); +} + +describe("clickTargetConnected", () => { + it("indexes the connected click target by its hotkey", async () => { + await setupController(); + + expect(controller().indexedClickTargets.get("a")).toBe(button()); + }); +}); + +describe("clickTargetDisconnected", () => { + it("removes the disconnected click target from the index", async () => { + await setupController(); + + button().remove(); + + await Promise.resolve(); + + expect(controller().indexedClickTargets.get("a")).toBeUndefined(); + }); +}); + +describe("handleKeydown", () => { + it("clicks the target for the pressed key", async () => { + await setupController(); + const clickSpy = vi.spyOn(button(), "click"); + + controller().handleKeydown(new KeyboardEvent("keydown", {key: "a"})); + + expect(clickSpy).toHaveBeenCalledWith(); + }); + + it("does nothing if there is no target for the pressed key", async () => { + await setupController(); + const clickSpy = vi.spyOn(button(), "click"); + + controller().handleKeydown(new KeyboardEvent("keydown", {key: "b"})); + + expect(clickSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/spec/javascript/helpers/assert_spec.ts b/spec/javascript/helpers/assert_spec.ts new file mode 100644 index 000000000..233d83557 --- /dev/null +++ b/spec/javascript/helpers/assert_spec.ts @@ -0,0 +1,33 @@ +import {describe, expect, it} from "vitest"; +import {assert} from "javascript/helpers"; + +describe("assert", () => { + it("throws an error when the passed value is null", () => { + expect(() => { assert(null); }).toThrow("value is null or undefined"); + }); + + it("throws an error when the passed value is undefined", () => { + expect(() => { assert(undefined); }).toThrow("value is null or undefined"); + }); + + it("does not throw an error when the passed value is 0", () => { + expect(() => { assert(0); }).not.toThrow(); + }); + + it("does not throw an error when the passed value is false", () => { + expect(() => { assert(false); }).not.toThrow(); + }); + + it("does not throw an error when the passed value is empty string", () => { + expect(() => { assert(""); }).not.toThrow(); + }); + + it("returns the passed value", () => { + expect(assert("blah")).toBe("blah"); + expect(assert(0)).toBe(0); + + const obj = {bloo: "blah"}; + + expect(assert(obj)).toBe(obj); + }); +}); diff --git a/spec/javascript/support/stimulus.ts b/spec/javascript/support/stimulus.ts new file mode 100644 index 000000000..be29ca49b --- /dev/null +++ b/spec/javascript/support/stimulus.ts @@ -0,0 +1,46 @@ +import {afterEach} from "vitest"; +import type {Context, Controller} from "@hotwired/stimulus"; +import {Application} from "@hotwired/stimulus"; + +import {assert} from "javascript/helpers"; + +let application: Application | null = null; + +type ControllerClass = new (context: Context) => T; + +async function bootStimulus( + name: string, + controller: ControllerClass, +): Promise { + application ??= Application.start(); + + application.register(name, controller); + application.handleError = (error: Error): void => { throw error; }; + + await Promise.resolve(); +} + +function getController( + element: HTMLElement, + name: string, + controllerClass: ControllerClass, +): T { + const controller = + assert(application).getControllerForElementAndIdentifier(element, name); + + if (controller instanceof controllerClass) { + return controller; + } else if (controller) { + throw new Error("Controller class does not match"); + } + + throw new Error("Controller not found"); +} + +afterEach(() => { + if (application) { application.stop(); } + + application = null; +}); + +export {bootStimulus, getController}; diff --git a/spec/javascript/vitest-globals.d.ts b/spec/javascript/vitest-globals.d.ts new file mode 100644 index 000000000..9896c472f --- /dev/null +++ b/spec/javascript/vitest-globals.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json index 0446d3a36..81a6a4a58 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "_test_helpers/*": ["spec/javascript/_test_helpers/*"], "controllers/*": ["app/javascript/controllers/*"] }, + "skipLibCheck": true, "sourceMap": true, "strict": true, "target": "es6",