Home Manual Reference Source Test

tests/controls.test.js

  1. import { describe, it, expect, vi } from "vitest";
  2. import { CONTROLS } from "../src/view/controls.js";
  3. import { STUBS } from "./api.js";
  4. /** @test {CONTROLS} */
  5. describe("CONTROLS", () => {
  6. const {
  7. stepBy,
  8. isDuplicatePart,
  9. duplicateSourceIndex,
  10. clampVoicePitchRange,
  11. setupChordTonesTristate,
  12. setupFeedbackTristates,
  13. } = CONTROLS;
  14. /** @test {CONTROLS#stepBy} */
  15. it("stepBy bumps params[name] by delta and clamps to [min, max]", () => {
  16. const params = { parts: 3 };
  17. const desc = { name: "parts", min: 1, max: 5 };
  18. stepBy(params, desc, 1);
  19. expect(params.parts).toBe(4);
  20. stepBy(params, desc, 10);
  21. expect(params.parts).toBe(5);
  22. stepBy(params, desc, -10);
  23. expect(params.parts).toBe(1);
  24. });
  25. /** @test {CONTROLS#clampVoicePitchRange} */
  26. it("clampVoicePitchRange snaps the just-edited field to the other when the range inverts; no-op when range is valid", () => {
  27. const v1 = { min_pitch: 70, max_pitch: 60 };
  28. clampVoicePitchRange(v1, "min_pitch");
  29. expect(v1.min_pitch).toBe(60);
  30. const v2 = { min_pitch: 70, max_pitch: 60 };
  31. clampVoicePitchRange(v2, "max_pitch");
  32. expect(v2.max_pitch).toBe(70);
  33. const v3 = { min_pitch: 50, max_pitch: 70 };
  34. clampVoicePitchRange(v3, "min_pitch");
  35. expect(v3).toEqual({ min_pitch: 50, max_pitch: 70 });
  36. });
  37. /** @test {CONTROLS#isDuplicatePart+duplicateSourceIndex} */
  38. it("isDuplicatePart + duplicateSourceIndex flag reused labels in 'parts'/'step1.parts' sections; 1-based source index, 0 when none, undefined-list-safe", () => {
  39. const parts = [{ label: "A" }, { label: "B" }, { label: "A" }];
  40. expect(isDuplicatePart(parts, 2, "parts")).toBe(true);
  41. expect(isDuplicatePart(parts, 1, "parts")).toBe(false);
  42. expect(isDuplicatePart(parts, 2, "voices")).toBe(false);
  43. expect(duplicateSourceIndex(parts, 2)).toBe(1);
  44. expect(duplicateSourceIndex(parts, 0)).toBe(0);
  45. expect(duplicateSourceIndex(undefined, 0)).toBe(0);
  46. expect(duplicateSourceIndex([{}], 0)).toBe(0);
  47. });
  48. /** @test {CONTROLS#setupChordTonesTristate} */
  49. it("setupChordTonesTristate cycles free to scale to chord on change; visual: chord = checked, scale = indeterminate, free = neither", () => {
  50. let value = "free";
  51. const el = STUBS.tristateInput();
  52. setupChordTonesTristate(
  53. el,
  54. () => value,
  55. (v) => (value = v),
  56. );
  57. expect(el).toMatchObject({ checked: false, indeterminate: false });
  58. const onChange = el.addEventListener.mock.calls[0][1];
  59. onChange();
  60. expect(value).toBe("scale");
  61. expect(el).toMatchObject({ checked: false, indeterminate: true });
  62. onChange();
  63. expect(value).toBe("chord");
  64. expect(el).toMatchObject({ checked: true, indeterminate: false });
  65. onChange();
  66. expect(value).toBe("free");
  67. });
  68. /** @test {CONTROLS#setupFeedbackTristates} */
  69. it("setupFeedbackTristates scans [data-tri-group] fieldsets, seeds triData[group][name] with 'neutral', toggles aria-invalid + tristate-* classes through the cycle", () => {
  70. const handlers = [];
  71. const cb = STUBS.tristateCheckbox(handlers);
  72. const fieldset = STUBS.tristateFieldset("questions", [cb]);
  73. const formEl = { querySelectorAll: vi.fn(() => [fieldset]) };
  74. const tri = {};
  75. setupFeedbackTristates(formEl, tri);
  76. expect(tri.questions.human).toBe("neutral");
  77. expect(cb.removeAttribute).toHaveBeenLastCalledWith("aria-invalid");
  78. handlers[0]();
  79. expect(tri.questions.human).toBe("negative");
  80. expect(cb.setAttribute).toHaveBeenLastCalledWith("aria-invalid", "true");
  81. expect(cb.classList.add).toHaveBeenLastCalledWith("tristate-negative");
  82. handlers[0]();
  83. expect(tri.questions.human).toBe("positive");
  84. expect(cb.setAttribute).toHaveBeenLastCalledWith("aria-invalid", "false");
  85. expect(cb.classList.add).toHaveBeenLastCalledWith("tristate-positive");
  86. handlers[0]();
  87. expect(tri.questions.human).toBe("neutral");
  88. });
  89. });