maybe don't delete collectstatic static/tests/ dir
This commit is contained in:
21
src/static/tests/LICENSE
Normal file
21
src/static/tests/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2026 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
477
src/static/tests/RoleSelectSpec.js
Normal file
477
src/static/tests/RoleSelectSpec.js
Normal file
@@ -0,0 +1,477 @@
|
||||
describe("RoleSelect", () => {
|
||||
let testDiv;
|
||||
|
||||
beforeEach(() => {
|
||||
testDiv = document.createElement("div");
|
||||
testDiv.innerHTML = `
|
||||
<div class="room-page"
|
||||
data-select-role-url="/epic/room/test-uuid/select-role">
|
||||
</div>
|
||||
<div id="id_inv_role_card"></div>
|
||||
`;
|
||||
document.body.appendChild(testDiv);
|
||||
window.fetch = jasmine.createSpy("fetch").and.returnValue(
|
||||
Promise.resolve({ ok: true })
|
||||
);
|
||||
// Default stub: auto-confirm so existing card-click tests pass unchanged.
|
||||
// The click-guard integration describe overrides this with a capturing spy.
|
||||
window.showGuard = (_anchor, _msg, onConfirm) => onConfirm && onConfirm();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
RoleSelect.closeFan();
|
||||
testDiv.remove();
|
||||
delete window.showGuard;
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// openFan() //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("openFan()", () => {
|
||||
it("creates .role-select-backdrop in the DOM", () => {
|
||||
RoleSelect.openFan();
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("creates #id_role_select inside the backdrop", () => {
|
||||
RoleSelect.openFan();
|
||||
expect(document.getElementById("id_role_select")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders exactly 6 .card elements", () => {
|
||||
RoleSelect.openFan();
|
||||
const cards = document.querySelectorAll("#id_role_select .card");
|
||||
expect(cards.length).toBe(6);
|
||||
});
|
||||
|
||||
it("does not open a second backdrop if already open", () => {
|
||||
RoleSelect.openFan();
|
||||
RoleSelect.openFan();
|
||||
expect(document.querySelectorAll(".role-select-backdrop").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// closeFan() //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("closeFan()", () => {
|
||||
it("removes .role-select-backdrop from the DOM", () => {
|
||||
RoleSelect.openFan();
|
||||
RoleSelect.closeFan();
|
||||
expect(document.querySelector(".role-select-backdrop")).toBeNull();
|
||||
});
|
||||
|
||||
it("removes #id_role_select from the DOM", () => {
|
||||
RoleSelect.openFan();
|
||||
RoleSelect.closeFan();
|
||||
expect(document.getElementById("id_role_select")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not throw if no fan is open", () => {
|
||||
expect(() => RoleSelect.closeFan()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Card interactions //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("card interactions", () => {
|
||||
beforeEach(() => {
|
||||
RoleSelect.openFan();
|
||||
});
|
||||
|
||||
it("mouseenter adds .flipped to the card", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.dispatchEvent(new MouseEvent("mouseenter"));
|
||||
expect(card.classList.contains("flipped")).toBe(true);
|
||||
});
|
||||
|
||||
it("mouseleave removes .flipped from the card", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.dispatchEvent(new MouseEvent("mouseenter"));
|
||||
card.dispatchEvent(new MouseEvent("mouseleave"));
|
||||
expect(card.classList.contains("flipped")).toBe(false);
|
||||
});
|
||||
|
||||
it("clicking a card closes the fan", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
expect(document.getElementById("id_role_select")).toBeNull();
|
||||
});
|
||||
|
||||
it("clicking a card appends a .card to #id_inv_role_card", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
expect(document.querySelector("#id_inv_role_card .card")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("clicking a card POSTs to the select_role URL", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
expect(window.fetch).toHaveBeenCalledWith(
|
||||
"/epic/room/test-uuid/select-role",
|
||||
jasmine.objectContaining({ method: "POST" })
|
||||
);
|
||||
});
|
||||
|
||||
it("clicking a card results in exactly one card in inventory", () => {
|
||||
const card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
expect(document.querySelectorAll("#id_inv_role_card .card").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Backdrop click //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("backdrop click", () => {
|
||||
it("closes the fan", () => {
|
||||
RoleSelect.openFan();
|
||||
document.querySelector(".role-select-backdrop").click();
|
||||
expect(document.getElementById("id_role_select")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not add a card to inventory", () => {
|
||||
RoleSelect.openFan();
|
||||
document.querySelector(".role-select-backdrop").click();
|
||||
expect(document.querySelector("#id_inv_role_card .card")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// room:roles_revealed event //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("room:roles_revealed event", () => {
|
||||
let reloadCalled;
|
||||
|
||||
beforeEach(() => {
|
||||
reloadCalled = false;
|
||||
RoleSelect.setReload(() => { reloadCalled = true; });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
RoleSelect.setReload(() => { window.location.reload(); });
|
||||
});
|
||||
|
||||
it("triggers a page reload", () => {
|
||||
window.dispatchEvent(new CustomEvent("room:roles_revealed", { detail: {} }));
|
||||
expect(reloadCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// room:turn_changed event //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("room:turn_changed event", () => {
|
||||
let stack;
|
||||
|
||||
beforeEach(() => {
|
||||
// Six table seats, slot 1 starts active
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const seat = document.createElement("div");
|
||||
seat.className = "table-seat" + (i === 1 ? " active" : "");
|
||||
seat.dataset.slot = String(i);
|
||||
seat.innerHTML = '<div class="seat-card-arc"></div>';
|
||||
testDiv.appendChild(seat);
|
||||
}
|
||||
stack = document.createElement("div");
|
||||
stack.className = "card-stack";
|
||||
stack.dataset.state = "ineligible";
|
||||
stack.dataset.userSlots = "1";
|
||||
stack.dataset.starterRoles = "";
|
||||
testDiv.appendChild(stack);
|
||||
});
|
||||
|
||||
it("calls Tray.forceClose() on turn change", () => {
|
||||
spyOn(Tray, "forceClose");
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 2 }
|
||||
}));
|
||||
expect(Tray.forceClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("moves .active to the newly active seat", () => {
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 2 }
|
||||
}));
|
||||
expect(
|
||||
testDiv.querySelector(".table-seat.active").dataset.slot
|
||||
).toBe("2");
|
||||
});
|
||||
|
||||
it("removes .active from the previously active seat", () => {
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 2 }
|
||||
}));
|
||||
expect(
|
||||
testDiv.querySelector(".table-seat[data-slot='1']").classList.contains("active")
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("sets data-state to eligible when active_slot matches user slot", () => {
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 1 }
|
||||
}));
|
||||
expect(stack.dataset.state).toBe("eligible");
|
||||
});
|
||||
|
||||
it("sets data-state to ineligible when active_slot does not match", () => {
|
||||
stack.dataset.state = "eligible";
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 2 }
|
||||
}));
|
||||
expect(stack.dataset.state).toBe("ineligible");
|
||||
});
|
||||
|
||||
it("clicking stack opens fan when newly eligible", () => {
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 1 }
|
||||
}));
|
||||
stack.click();
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("clicking stack does not open fan when ineligible", () => {
|
||||
// Make eligible first (adds listener), then flip back to ineligible
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 1 }
|
||||
}));
|
||||
window.dispatchEvent(new CustomEvent("room:turn_changed", {
|
||||
detail: { active_slot: 2 }
|
||||
}));
|
||||
stack.click();
|
||||
expect(document.querySelector(".role-select-backdrop")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Tray card placement after successful role selection //
|
||||
// ------------------------------------------------------------------ //
|
||||
// The tray-role-card is created in the fetch .then() callback, so //
|
||||
// these tests are async — await Promise.resolve() flushes the //
|
||||
// microtask queue before asserting. //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("tray card after successful role selection", () => {
|
||||
let grid, guardConfirm;
|
||||
|
||||
beforeEach(() => {
|
||||
// Minimal tray grid matching room.html structure
|
||||
grid = document.createElement("div");
|
||||
grid.id = "id_tray_grid";
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "tray-cell";
|
||||
grid.appendChild(cell);
|
||||
}
|
||||
testDiv.appendChild(grid);
|
||||
|
||||
spyOn(Tray, "open");
|
||||
|
||||
// Capturing guard spy — holds onConfirm so we can fire it per-test
|
||||
window.showGuard = jasmine.createSpy("showGuard").and.callFake(
|
||||
(anchor, message, onConfirm) => { guardConfirm = onConfirm; }
|
||||
);
|
||||
|
||||
RoleSelect.openFan();
|
||||
document.querySelector("#id_role_select .card").click();
|
||||
});
|
||||
|
||||
it("prepends a .tray-role-card to #id_tray_grid on success", async () => {
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(grid.querySelector(".tray-role-card")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("tray-role-card is the first child of #id_tray_grid", async () => {
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(grid.firstElementChild.classList.contains("tray-role-card")).toBe(true);
|
||||
});
|
||||
|
||||
it("tray-role-card carries the selected role as data-role", async () => {
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
const trayCard = grid.querySelector(".tray-role-card");
|
||||
expect(trayCard.dataset.role).toBeTruthy();
|
||||
});
|
||||
|
||||
it("calls Tray.open() on success", async () => {
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(Tray.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not prepend a tray-role-card on server rejection", async () => {
|
||||
window.fetch = jasmine.createSpy("fetch").and.returnValue(
|
||||
Promise.resolve({ ok: false })
|
||||
);
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(grid.querySelector(".tray-role-card")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not call Tray.open() on server rejection", async () => {
|
||||
window.fetch = jasmine.createSpy("fetch").and.returnValue(
|
||||
Promise.resolve({ ok: false })
|
||||
);
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(Tray.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("grid grows by exactly 1 on success", async () => {
|
||||
const before = grid.children.length;
|
||||
guardConfirm();
|
||||
await Promise.resolve();
|
||||
expect(grid.children.length).toBe(before + 1);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// click-guard integration //
|
||||
// ------------------------------------------------------------------ //
|
||||
// NOTE: cascade prevention (outside-click on backdrop not closing the //
|
||||
// fan while the guard is active) relies on the guard portal's capture- //
|
||||
// phase stopPropagation, which lives in base.html and requires //
|
||||
// integration testing. The callback contract is fully covered below. //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("click-guard integration", () => {
|
||||
let guardAnchor, guardMessage, guardConfirm, guardDismiss;
|
||||
|
||||
beforeEach(() => {
|
||||
window.showGuard = jasmine.createSpy("showGuard").and.callFake(
|
||||
(anchor, message, onConfirm, onDismiss) => {
|
||||
guardAnchor = anchor;
|
||||
guardMessage = message;
|
||||
guardConfirm = onConfirm;
|
||||
guardDismiss = onDismiss;
|
||||
}
|
||||
);
|
||||
RoleSelect.openFan();
|
||||
});
|
||||
|
||||
describe("clicking a card", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
});
|
||||
|
||||
it("calls window.showGuard", () => {
|
||||
expect(window.showGuard).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes the card element as the anchor", () => {
|
||||
expect(guardAnchor).toBe(card);
|
||||
});
|
||||
|
||||
it("message contains the role name", () => {
|
||||
const roleName = card.querySelector(".card-role-name").textContent.trim();
|
||||
expect(guardMessage).toContain(roleName);
|
||||
});
|
||||
|
||||
it("message contains the role code", () => {
|
||||
expect(guardMessage).toContain(card.dataset.role);
|
||||
});
|
||||
|
||||
it("message contains a <br>", () => {
|
||||
expect(guardMessage).toContain("<br>");
|
||||
});
|
||||
|
||||
it("does not immediately close the fan", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not immediately POST to the select_role URL", () => {
|
||||
expect(window.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds .flipped to the card", () => {
|
||||
expect(card.classList.contains("flipped")).toBe(true);
|
||||
});
|
||||
|
||||
it("adds .guard-active to the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(true);
|
||||
});
|
||||
|
||||
it("mouseleave does not remove .flipped while guard is active", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseleave"));
|
||||
expect(card.classList.contains("flipped")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("confirming the guard (OK)", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
guardConfirm();
|
||||
});
|
||||
|
||||
it("removes .guard-active from the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(false);
|
||||
});
|
||||
|
||||
it("closes the fan", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).toBeNull();
|
||||
});
|
||||
|
||||
it("POSTs to the select_role URL", () => {
|
||||
expect(window.fetch).toHaveBeenCalledWith(
|
||||
"/epic/room/test-uuid/select-role",
|
||||
jasmine.objectContaining({ method: "POST" })
|
||||
);
|
||||
});
|
||||
|
||||
it("appends a .card to #id_inv_role_card", () => {
|
||||
expect(document.querySelector("#id_inv_role_card .card")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("dismissing the guard (NVM or outside click)", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
guardDismiss();
|
||||
});
|
||||
|
||||
it("removes .guard-active from the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .flipped from the card", () => {
|
||||
expect(card.classList.contains("flipped")).toBe(false);
|
||||
});
|
||||
|
||||
it("leaves the fan open", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not POST to the select_role URL", () => {
|
||||
expect(window.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not add a card to inventory", () => {
|
||||
expect(document.querySelector("#id_inv_role_card .card")).toBeNull();
|
||||
});
|
||||
|
||||
it("restores normal mouseleave behaviour on the card", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseenter"));
|
||||
card.dispatchEvent(new MouseEvent("mouseleave"));
|
||||
expect(card.classList.contains("flipped")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
57
src/static/tests/Spec.js
Normal file
57
src/static/tests/Spec.js
Normal file
@@ -0,0 +1,57 @@
|
||||
console.log("Spec.js is loading");
|
||||
|
||||
describe("GameArray JavaScript", () => {
|
||||
const inputId= "id_text";
|
||||
const errorClass = "invalid-feedback";
|
||||
const inputSelector = `#${inputId}`;
|
||||
const errorSelector = `.${errorClass}`;
|
||||
let testDiv;
|
||||
let textInput;
|
||||
let errorMsg;
|
||||
|
||||
beforeEach(() => {
|
||||
console.log("beforeEach");
|
||||
testDiv = document.createElement("div");
|
||||
testDiv.innerHTML = `
|
||||
<form>
|
||||
<input
|
||||
id="${inputId}"
|
||||
name="text"
|
||||
class="form-control form-control-lg is-invalid"
|
||||
placeholder="Enter a to-do item"
|
||||
value="Value as submitted"
|
||||
aria-describedby="id_text_feedback"
|
||||
required
|
||||
/>
|
||||
<div id="id_text_feedback" class="${errorClass}">An error message</div>
|
||||
</form>
|
||||
`;
|
||||
document.body.appendChild(testDiv);
|
||||
textInput = document.querySelector(inputSelector);
|
||||
errorMsg = document.querySelector(errorSelector);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testDiv.remove();
|
||||
});
|
||||
|
||||
it("should have a useful html fixture", () => {
|
||||
console.log("in test 1");
|
||||
expect(errorMsg.checkVisibility()).toBe(true);
|
||||
});
|
||||
|
||||
it("should hide error message on input", () => {
|
||||
console.log("in test 2");
|
||||
initialize(inputSelector);
|
||||
textInput.dispatchEvent(new InputEvent("input"));
|
||||
|
||||
expect(errorMsg.checkVisibility()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not hide error message before event is fired", () => {
|
||||
console.log("in test 3");
|
||||
initialize(inputSelector);
|
||||
|
||||
expect(errorMsg.checkVisibility()).toBe(true);
|
||||
});
|
||||
});
|
||||
38
src/static/tests/SpecRunner.html
Normal file
38
src/static/tests/SpecRunner.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="author" content="Disco DeDisco">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="lib/jasmine-6.0.1/jasmine.css">
|
||||
|
||||
<title>Jasmine Spec Runner</title>
|
||||
<link rel="stylesheet" href="lib/jasmine.css">
|
||||
|
||||
<!-- Jasmine -->
|
||||
<script src="lib/jasmine-6.0.1/jasmine.js"></script>
|
||||
<script src="lib/jasmine-6.0.1/jasmine-html.js"></script>
|
||||
<script src="lib/jasmine-6.0.1/boot0.js"></script>
|
||||
<!-- spec files -->
|
||||
<script src="Spec.js"></script>
|
||||
<script src="RoleSelectSpec.js"></script>
|
||||
<script src="TraySpec.js"></script>
|
||||
<!-- src files -->
|
||||
<script src="/static/apps/dashboard/dashboard.js"></script>
|
||||
<script src="/static/apps/epic/role-select.js"></script>
|
||||
<script src="/static/apps/epic/tray.js"></script>
|
||||
<!-- Jasmine env config (optional) -->
|
||||
<script src="lib/jasmine-6.0.1/boot1.js"></script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
425
src/static/tests/TraySpec.js
Normal file
425
src/static/tests/TraySpec.js
Normal file
@@ -0,0 +1,425 @@
|
||||
// ── TraySpec.js ───────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Unit specs for tray.js — the per-seat, per-room slide-out panel anchored
|
||||
// to the right edge of the viewport.
|
||||
//
|
||||
// DOM contract assumed by the module:
|
||||
// #id_tray_wrap — outermost container; JS sets style.left for positioning
|
||||
// #id_tray_btn — the drawer-handle button
|
||||
// #id_tray — the tray panel (hidden by default)
|
||||
//
|
||||
// Public API under test:
|
||||
// Tray.init() — compute bounds, apply vertical bounds, attach listeners
|
||||
// Tray.open() — reveal tray, animate wrap to minLeft
|
||||
// Tray.close() — hide tray, animate wrap to maxLeft
|
||||
// Tray.isOpen() — state predicate
|
||||
// Tray.reset() — restore initial state (for afterEach)
|
||||
//
|
||||
// Drag model: tray follows pointer in real-time; position persists on release.
|
||||
// Any leftward drag opens the tray.
|
||||
// Drag > 10px suppresses the subsequent click event.
|
||||
//
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("Tray", () => {
|
||||
let btn, tray, wrap;
|
||||
|
||||
beforeEach(() => {
|
||||
wrap = document.createElement("div");
|
||||
wrap.id = "id_tray_wrap";
|
||||
|
||||
btn = document.createElement("button");
|
||||
btn.id = "id_tray_btn";
|
||||
|
||||
tray = document.createElement("div");
|
||||
tray.id = "id_tray";
|
||||
tray.style.display = "none";
|
||||
|
||||
wrap.appendChild(btn);
|
||||
document.body.appendChild(wrap);
|
||||
document.body.appendChild(tray);
|
||||
|
||||
Tray._testSetLandscape(false); // force portrait regardless of window size
|
||||
Tray.init();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Tray.reset();
|
||||
wrap.remove();
|
||||
tray.remove();
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// open() //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("open()", () => {
|
||||
it("makes #id_tray visible", () => {
|
||||
Tray.open();
|
||||
expect(tray.style.display).not.toBe("none");
|
||||
});
|
||||
|
||||
it("adds .open to #id_tray_btn", () => {
|
||||
Tray.open();
|
||||
expect(btn.classList.contains("open")).toBe(true);
|
||||
});
|
||||
|
||||
it("sets wrap left to minLeft (0)", () => {
|
||||
Tray.open();
|
||||
expect(wrap.style.left).toBe("0px");
|
||||
});
|
||||
|
||||
it("calling open() twice does not duplicate .open", () => {
|
||||
Tray.open();
|
||||
Tray.open();
|
||||
const openCount = btn.className.split(" ").filter(c => c === "open").length;
|
||||
expect(openCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// close() //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("close()", () => {
|
||||
beforeEach(() => Tray.open());
|
||||
|
||||
it("hides #id_tray after slide + snap both complete", () => {
|
||||
Tray.close();
|
||||
wrap.dispatchEvent(new TransitionEvent("transitionend", { propertyName: "left" }));
|
||||
wrap.dispatchEvent(new Event("animationend"));
|
||||
expect(tray.style.display).toBe("none");
|
||||
});
|
||||
|
||||
it("adds .snap to wrap after slide transition completes", () => {
|
||||
Tray.close();
|
||||
wrap.dispatchEvent(new TransitionEvent("transitionend", { propertyName: "left" }));
|
||||
expect(wrap.classList.contains("snap")).toBe(true);
|
||||
});
|
||||
|
||||
it("removes .snap from wrap once animationend fires", () => {
|
||||
Tray.close();
|
||||
wrap.dispatchEvent(new TransitionEvent("transitionend", { propertyName: "left" }));
|
||||
wrap.dispatchEvent(new Event("animationend"));
|
||||
expect(wrap.classList.contains("snap")).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .open from #id_tray_btn", () => {
|
||||
Tray.close();
|
||||
expect(btn.classList.contains("open")).toBe(false);
|
||||
});
|
||||
|
||||
it("sets wrap left to maxLeft", () => {
|
||||
Tray.close();
|
||||
expect(parseInt(wrap.style.left, 10)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("does not throw if already closed", () => {
|
||||
Tray.close();
|
||||
expect(() => Tray.close()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// isOpen() //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("isOpen()", () => {
|
||||
it("returns false by default", () => {
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true after open()", () => {
|
||||
Tray.open();
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false after close()", () => {
|
||||
Tray.open();
|
||||
Tray.close();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Click when closed — wobble wrap, do not open //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("clicking btn when closed", () => {
|
||||
it("adds .wobble to wrap", () => {
|
||||
btn.click();
|
||||
expect(wrap.classList.contains("wobble")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not open the tray", () => {
|
||||
btn.click();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .wobble once animationend fires on wrap", () => {
|
||||
btn.click();
|
||||
wrap.dispatchEvent(new Event("animationend"));
|
||||
expect(wrap.classList.contains("wobble")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Click when open — close, no wobble //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("clicking btn when open", () => {
|
||||
beforeEach(() => Tray.open());
|
||||
|
||||
it("closes the tray", () => {
|
||||
btn.click();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not add .wobble", () => {
|
||||
btn.click();
|
||||
expect(wrap.classList.contains("wobble")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Drag interaction — continuous positioning //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("drag interaction", () => {
|
||||
function simulateDrag(deltaX) {
|
||||
const startX = 800;
|
||||
btn.dispatchEvent(new PointerEvent("pointerdown", { clientX: startX, bubbles: true }));
|
||||
btn.dispatchEvent(new PointerEvent("pointermove", { clientX: startX + deltaX, bubbles: true }));
|
||||
btn.dispatchEvent(new PointerEvent("pointerup", { clientX: startX + deltaX, bubbles: true }));
|
||||
}
|
||||
|
||||
it("dragging left opens the tray", () => {
|
||||
simulateDrag(-60);
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("any leftward drag opens the tray", () => {
|
||||
simulateDrag(-20);
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("dragging right does not open the tray", () => {
|
||||
simulateDrag(100);
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("drag > 10px suppresses the subsequent click", () => {
|
||||
simulateDrag(-60);
|
||||
btn.click(); // should be swallowed — tray stays open
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not add .wobble during drag", () => {
|
||||
simulateDrag(-60);
|
||||
expect(wrap.classList.contains("wobble")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Landscape mode — Y-axis drag, top-positioned wrap //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("landscape mode", () => {
|
||||
// Re-init in landscape after the portrait init from outer beforeEach.
|
||||
beforeEach(() => {
|
||||
Tray.reset();
|
||||
Tray._testSetLandscape(true);
|
||||
Tray.init();
|
||||
});
|
||||
|
||||
function simulateDragY(deltaY) {
|
||||
const startY = 50;
|
||||
btn.dispatchEvent(new PointerEvent("pointerdown", { clientY: startY, clientX: 0, bubbles: true }));
|
||||
btn.dispatchEvent(new PointerEvent("pointermove", { clientY: startY + deltaY, clientX: 0, bubbles: true }));
|
||||
btn.dispatchEvent(new PointerEvent("pointerup", { clientY: startY + deltaY, clientX: 0, bubbles: true }));
|
||||
}
|
||||
|
||||
// ── open() in landscape ─────────────────────────────────────────── //
|
||||
|
||||
describe("open()", () => {
|
||||
it("makes #id_tray visible", () => {
|
||||
Tray.open();
|
||||
expect(tray.style.display).not.toBe("none");
|
||||
});
|
||||
|
||||
it("adds .open to #id_tray_btn", () => {
|
||||
Tray.open();
|
||||
expect(btn.classList.contains("open")).toBe(true);
|
||||
});
|
||||
|
||||
it("positions wrap via style.top, not style.left", () => {
|
||||
Tray.open();
|
||||
expect(wrap.style.top).not.toBe("");
|
||||
expect(wrap.style.left).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// ── close() in landscape ────────────────────────────────────────── //
|
||||
|
||||
describe("close()", () => {
|
||||
beforeEach(() => Tray.open());
|
||||
|
||||
it("closes the tray (display not toggled in landscape)", () => {
|
||||
Tray.close();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .open from #id_tray_btn", () => {
|
||||
Tray.close();
|
||||
expect(btn.classList.contains("open")).toBe(false);
|
||||
});
|
||||
|
||||
it("closed top is less than open top (wrap slides up to close)", () => {
|
||||
const openTop = parseInt(wrap.style.top, 10);
|
||||
Tray.close();
|
||||
const closedTop = parseInt(wrap.style.top, 10);
|
||||
expect(closedTop).toBeLessThan(openTop);
|
||||
});
|
||||
|
||||
it("adds .snap to wrap after top transition completes", () => {
|
||||
Tray.close();
|
||||
wrap.dispatchEvent(new TransitionEvent("transitionend", { propertyName: "top" }));
|
||||
expect(wrap.classList.contains("snap")).toBe(true);
|
||||
});
|
||||
|
||||
it("removes .snap from wrap once animationend fires", () => {
|
||||
Tray.close();
|
||||
wrap.dispatchEvent(new TransitionEvent("transitionend", { propertyName: "top" }));
|
||||
wrap.dispatchEvent(new Event("animationend"));
|
||||
expect(wrap.classList.contains("snap")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── drag — Y axis ──────────────────────────────────────────────── //
|
||||
|
||||
describe("drag interaction", () => {
|
||||
it("dragging down opens the tray", () => {
|
||||
simulateDragY(100);
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("dragging up does not open the tray", () => {
|
||||
simulateDragY(-100);
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("drag > 10px downward suppresses subsequent click", () => {
|
||||
simulateDragY(100);
|
||||
btn.click(); // should be swallowed — tray stays open
|
||||
expect(Tray.isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not set style.left (Y axis only)", () => {
|
||||
simulateDragY(100);
|
||||
expect(wrap.style.left).toBe("");
|
||||
});
|
||||
|
||||
it("does not add .wobble during drag", () => {
|
||||
simulateDragY(100);
|
||||
expect(wrap.classList.contains("wobble")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── click when closed — wobble, no open ───────────────────────── //
|
||||
|
||||
describe("clicking btn when closed", () => {
|
||||
it("adds .wobble to wrap", () => {
|
||||
btn.click();
|
||||
expect(wrap.classList.contains("wobble")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not open the tray", () => {
|
||||
btn.click();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── click when open — close ────────────────────────────────────── //
|
||||
|
||||
describe("clicking btn when open", () => {
|
||||
beforeEach(() => Tray.open());
|
||||
|
||||
it("closes the tray", () => {
|
||||
btn.click();
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── init positions wrap at closed (top) ────────────────────────── //
|
||||
|
||||
it("init sets wrap to closed position (top < 0 or = maxTop)", () => {
|
||||
// After landscape init with no real elements, _maxTop = -(wrapH_fallback - handleH_fallback)
|
||||
// which will be negative. Wrap starts off-screen above.
|
||||
const top = parseInt(wrap.style.top, 10);
|
||||
expect(top).toBeLessThan(0);
|
||||
});
|
||||
|
||||
// ── resize closes landscape tray ─────────────────────────────── //
|
||||
|
||||
describe("resize closes the tray", () => {
|
||||
it("closes when landscape tray is open", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .open from btn on resize", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(btn.classList.contains("open")).toBe(false);
|
||||
});
|
||||
|
||||
it("resets wrap to closed top position on resize", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(parseInt(wrap.style.top, 10)).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it("does not re-open a closed tray on resize", () => {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// window resize — portrait //
|
||||
// ---------------------------------------------------------------------- //
|
||||
|
||||
describe("window resize (portrait)", () => {
|
||||
it("closes the tray when open", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .open from btn on resize", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(btn.classList.contains("open")).toBe(false);
|
||||
});
|
||||
|
||||
it("hides the tray panel on resize", () => {
|
||||
Tray.open();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(tray.style.display).toBe("none");
|
||||
});
|
||||
|
||||
it("resets wrap to closed left position on resize", () => {
|
||||
Tray.open();
|
||||
expect(wrap.style.left).toBe("0px");
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(parseInt(wrap.style.left, 10)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("does not re-open a closed tray on resize", () => {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
expect(Tray.isOpen()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
68
src/static/tests/lib/jasmine-6.0.1/boot0.js
Normal file
68
src/static/tests/lib/jasmine-6.0.1/boot0.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2026 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
This file starts the process of "booting" Jasmine. It initializes Jasmine,
|
||||
makes its globals available, and creates the env. This file should be loaded
|
||||
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
|
||||
source files or spec files are loaded.
|
||||
*/
|
||||
(function() {
|
||||
const jasmineRequire = window.jasmineRequire || require('./jasmine.js');
|
||||
|
||||
/**
|
||||
* ## Require & Instantiate
|
||||
*
|
||||
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
||||
*/
|
||||
const jasmine = jasmineRequire.core(jasmineRequire),
|
||||
global = jasmine.getGlobal();
|
||||
global.jasmine = jasmine;
|
||||
|
||||
/**
|
||||
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
||||
*/
|
||||
jasmineRequire.html(jasmine);
|
||||
|
||||
/**
|
||||
* Create the Jasmine environment. This is used to run all specs in a project.
|
||||
*/
|
||||
const env = jasmine.getEnv();
|
||||
|
||||
/**
|
||||
* ## The Global Interface
|
||||
*
|
||||
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
||||
*/
|
||||
const jasmineInterface = jasmineRequire.interface(jasmine, env);
|
||||
|
||||
/**
|
||||
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
||||
*/
|
||||
for (const property in jasmineInterface) {
|
||||
global[property] = jasmineInterface[property];
|
||||
}
|
||||
})();
|
||||
64
src/static/tests/lib/jasmine-6.0.1/boot1.js
Normal file
64
src/static/tests/lib/jasmine-6.0.1/boot1.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2026 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
This file finishes 'booting' Jasmine, performing all of the necessary
|
||||
initialization before executing the loaded environment and all of a project's
|
||||
specs. This file should be loaded after `boot0.js` but before any project
|
||||
source files or spec files are loaded. Thus this file can also be used to
|
||||
customize Jasmine for a project.
|
||||
|
||||
If a project is using Jasmine via the standalone distribution, this file can
|
||||
be customized directly. If you only wish to configure the Jasmine env, you
|
||||
can load another file that calls `jasmine.getEnv().configure({...})`
|
||||
after `boot0.js` is loaded and before this file is loaded.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const env = jasmine.getEnv();
|
||||
const urls = new jasmine.HtmlReporterV2Urls();
|
||||
|
||||
/**
|
||||
* Configures Jasmine based on the current set of query parameters. This
|
||||
* supports all parameters set by the HTML reporter as well as
|
||||
* spec=partialPath, which filters out specs whose paths don't contain the
|
||||
* parameter.
|
||||
*/
|
||||
env.configure(urls.configFromCurrentUrl());
|
||||
|
||||
const currentWindowOnload = window.onload;
|
||||
window.onload = function() {
|
||||
if (currentWindowOnload) {
|
||||
currentWindowOnload();
|
||||
}
|
||||
|
||||
// The HTML reporter needs to be set up here so it can access the DOM. Other
|
||||
// reporters can be added at any time before env.execute() is called.
|
||||
const htmlReporter = new jasmine.HtmlReporterV2({ env, urls });
|
||||
env.addReporter(htmlReporter);
|
||||
env.execute();
|
||||
};
|
||||
})();
|
||||
1863
src/static/tests/lib/jasmine-6.0.1/jasmine-html.js
Normal file
1863
src/static/tests/lib/jasmine-6.0.1/jasmine-html.js
Normal file
File diff suppressed because it is too large
Load Diff
351
src/static/tests/lib/jasmine-6.0.1/jasmine.css
Normal file
351
src/static/tests/lib/jasmine-6.0.1/jasmine.css
Normal file
File diff suppressed because one or more lines are too long
12412
src/static/tests/lib/jasmine-6.0.1/jasmine.js
Normal file
12412
src/static/tests/lib/jasmine-6.0.1/jasmine.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user