mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-17 00:43:48 +08:00
user tests
This commit is contained in:
parent
cb91a6eecd
commit
325008a57d
223
tests-ui/tests/users.test.js
Normal file
223
tests-ui/tests/users.test.js
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// @ts-check
|
||||||
|
/// <reference path="../node_modules/@types/jest/index.d.ts" />
|
||||||
|
const { start } = require("../utils");
|
||||||
|
const lg = require("../utils/litegraph");
|
||||||
|
|
||||||
|
describe("users", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
lg.setup(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
lg.teardown(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectNoUserScreen() {
|
||||||
|
// Ensure login isnt visible
|
||||||
|
const selection = document.querySelectorAll("#comfy-user-selection")?.[0];
|
||||||
|
expect(selection["style"].display).toBe("none");
|
||||||
|
const menu = document.querySelectorAll(".comfy-menu")?.[0];
|
||||||
|
expect(window.getComputedStyle(menu)?.display).not.toBe("none");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("multi-user", () => {
|
||||||
|
async function testUserScreen(onShown, users) {
|
||||||
|
if (!users) {
|
||||||
|
users = {};
|
||||||
|
}
|
||||||
|
const starting = start({
|
||||||
|
resetEnv: true,
|
||||||
|
users,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure no current user
|
||||||
|
expect(localStorage["Comfy.userId"]).toBeFalsy();
|
||||||
|
expect(localStorage["Comfy.userName"]).toBeFalsy();
|
||||||
|
|
||||||
|
await new Promise(process.nextTick); // wait for promises to resolve
|
||||||
|
|
||||||
|
const selection = document.querySelectorAll("#comfy-user-selection")?.[0];
|
||||||
|
expect(selection).toBeTruthy();
|
||||||
|
|
||||||
|
// Ensure login is visible
|
||||||
|
expect(window.getComputedStyle(selection)?.display).not.toBe("none");
|
||||||
|
// Ensure menu is hidden
|
||||||
|
const menu = document.querySelectorAll(".comfy-menu")?.[0];
|
||||||
|
expect(window.getComputedStyle(menu)?.display).toBe("none");
|
||||||
|
|
||||||
|
const isCreate = await onShown(selection);
|
||||||
|
|
||||||
|
// Submit form
|
||||||
|
selection.querySelectorAll("form")[0].submit();
|
||||||
|
await new Promise(process.nextTick); // wait for promises to resolve
|
||||||
|
|
||||||
|
// Wait for start
|
||||||
|
const s = await starting;
|
||||||
|
|
||||||
|
// Ensure login is removed
|
||||||
|
expect(document.querySelectorAll("#comfy-user-selection")).toHaveLength(0);
|
||||||
|
expect(window.getComputedStyle(menu)?.display).not.toBe("none");
|
||||||
|
|
||||||
|
// Ensure settings + templates are saved
|
||||||
|
const { api } = require("../../web/scripts/api");
|
||||||
|
expect(api.createUser).toHaveBeenCalledTimes(+isCreate);
|
||||||
|
expect(api.storeSettings).toHaveBeenCalledTimes(+isCreate);
|
||||||
|
expect(api.storeUserData).toHaveBeenCalledTimes(+isCreate);
|
||||||
|
if (isCreate) {
|
||||||
|
expect(api.storeUserData).toHaveBeenCalledWith("comfy.templates.json", null, { stringify: false });
|
||||||
|
expect(s.app.isNewUserSession).toBeTruthy();
|
||||||
|
} else {
|
||||||
|
expect(s.app.isNewUserSession).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { users, selection, ...s };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("allows user creation if no users", async () => {
|
||||||
|
const { users } = await testUserScreen((selection) => {
|
||||||
|
// Ensure we have no users flag added
|
||||||
|
expect(selection.classList.contains("no-users")).toBeTruthy();
|
||||||
|
|
||||||
|
// Enter a username
|
||||||
|
const input = selection.getElementsByTagName("input")[0];
|
||||||
|
input.focus();
|
||||||
|
input.value = "Test User";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(users).toStrictEqual({
|
||||||
|
"Test User!": "Test User",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(localStorage["Comfy.userId"]).toBe("Test User!");
|
||||||
|
expect(localStorage["Comfy.userName"]).toBe("Test User");
|
||||||
|
});
|
||||||
|
it("allows user creation if no current user but other users", async () => {
|
||||||
|
const users = {
|
||||||
|
"Test User 2!": "Test User 2",
|
||||||
|
};
|
||||||
|
|
||||||
|
await testUserScreen((selection) => {
|
||||||
|
expect(selection.classList.contains("no-users")).toBeFalsy();
|
||||||
|
|
||||||
|
// Enter a username
|
||||||
|
const input = selection.getElementsByTagName("input")[0];
|
||||||
|
input.focus();
|
||||||
|
input.value = "Test User 3";
|
||||||
|
return true;
|
||||||
|
}, users);
|
||||||
|
|
||||||
|
expect(users).toStrictEqual({
|
||||||
|
"Test User 2!": "Test User 2",
|
||||||
|
"Test User 3!": "Test User 3",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(localStorage["Comfy.userId"]).toBe("Test User 3!");
|
||||||
|
expect(localStorage["Comfy.userName"]).toBe("Test User 3");
|
||||||
|
});
|
||||||
|
it("allows user selection if no current user but other users", async () => {
|
||||||
|
const users = {
|
||||||
|
"A!": "A",
|
||||||
|
"B!": "B",
|
||||||
|
"C!": "C",
|
||||||
|
};
|
||||||
|
|
||||||
|
await testUserScreen((selection) => {
|
||||||
|
expect(selection.classList.contains("no-users")).toBeFalsy();
|
||||||
|
|
||||||
|
// Check user list
|
||||||
|
const select = selection.getElementsByTagName("select")[0];
|
||||||
|
const options = select.getElementsByTagName("option");
|
||||||
|
expect(
|
||||||
|
[...options]
|
||||||
|
.filter((o) => !o.disabled)
|
||||||
|
.reduce((p, n) => {
|
||||||
|
p[n.getAttribute("value")] = n.textContent;
|
||||||
|
return p;
|
||||||
|
}, {})
|
||||||
|
).toStrictEqual(users);
|
||||||
|
|
||||||
|
// Select an option
|
||||||
|
select.focus();
|
||||||
|
select.value = options[2].value;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}, users);
|
||||||
|
|
||||||
|
expect(users).toStrictEqual(users);
|
||||||
|
|
||||||
|
expect(localStorage["Comfy.userId"]).toBe("B!");
|
||||||
|
expect(localStorage["Comfy.userName"]).toBe("B");
|
||||||
|
});
|
||||||
|
it("doesnt show user screen if current user", async () => {
|
||||||
|
const starting = start({
|
||||||
|
resetEnv: true,
|
||||||
|
users: {
|
||||||
|
"User!": "User",
|
||||||
|
},
|
||||||
|
localStorage: {
|
||||||
|
"Comfy.userId": "User!",
|
||||||
|
"Comfy.userName": "User",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await new Promise(process.nextTick); // wait for promises to resolve
|
||||||
|
|
||||||
|
expectNoUserScreen();
|
||||||
|
|
||||||
|
await starting;
|
||||||
|
});
|
||||||
|
it("allows user switching", async () => {
|
||||||
|
const { app } = await start({
|
||||||
|
resetEnv: true,
|
||||||
|
users: {
|
||||||
|
"User!": "User",
|
||||||
|
},
|
||||||
|
localStorage: {
|
||||||
|
"Comfy.userId": "User!",
|
||||||
|
"Comfy.userName": "User",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// cant actually test switching user easily but can check the setting is present
|
||||||
|
expect(app.ui.settings.settingsLookup["Comfy.SwitchUser"]).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("single-user", () => {
|
||||||
|
it("doesnt show user creation if no default user", async () => {
|
||||||
|
const { app } = await start({
|
||||||
|
resetEnv: true,
|
||||||
|
users: false,
|
||||||
|
});
|
||||||
|
expectNoUserScreen();
|
||||||
|
|
||||||
|
// It should store the settings
|
||||||
|
const { api } = require("../../web/scripts/api");
|
||||||
|
expect(api.storeSettings).toHaveBeenCalledTimes(1);
|
||||||
|
expect(api.storeUserData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(api.storeUserData).toHaveBeenCalledWith("comfy.templates.json", null, { stringify: false });
|
||||||
|
expect(app.isNewUserSession).toBeTruthy();
|
||||||
|
});
|
||||||
|
it("doesnt show user creation if default user", async () => {
|
||||||
|
const { app } = await start({
|
||||||
|
resetEnv: true,
|
||||||
|
users: true,
|
||||||
|
});
|
||||||
|
expectNoUserScreen();
|
||||||
|
|
||||||
|
// It should store the settings
|
||||||
|
const { api } = require("../../web/scripts/api");
|
||||||
|
expect(api.storeSettings).toHaveBeenCalledTimes(0);
|
||||||
|
expect(api.storeUserData).toHaveBeenCalledTimes(0);
|
||||||
|
expect(app.isNewUserSession).toBeFalsy();
|
||||||
|
});
|
||||||
|
it("doesnt allow user switching", async () => {
|
||||||
|
const { app } = await start({
|
||||||
|
resetEnv: true,
|
||||||
|
});
|
||||||
|
expectNoUserScreen();
|
||||||
|
|
||||||
|
expect(app.ui.settings.settingsLookup["Comfy.SwitchUser"]).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,10 +1,18 @@
|
|||||||
const { mockApi } = require("./setup");
|
const { mockApi } = require("./setup");
|
||||||
const { Ez } = require("./ezgraph");
|
const { Ez } = require("./ezgraph");
|
||||||
const lg = require("./litegraph");
|
const lg = require("./litegraph");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const html = fs.readFileSync(path.resolve(__dirname, "../../web/index.html"))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param { Parameters<mockApi>[0] & { resetEnv?: boolean, preSetup?(app): Promise<void> } } config
|
* @param { Parameters<typeof mockApi>[0] & {
|
||||||
|
* resetEnv?: boolean,
|
||||||
|
* preSetup?(app): Promise<void>,
|
||||||
|
* localStorage?: Record<string, string>
|
||||||
|
* } } config
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function start(config = {}) {
|
export async function start(config = {}) {
|
||||||
@ -12,8 +20,14 @@ export async function start(config = {}) {
|
|||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
lg.setup(global);
|
lg.setup(global);
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(localStorage, config.localStorage ?? {});
|
||||||
|
document.body.innerHTML = html;
|
||||||
|
|
||||||
|
mockUtils();
|
||||||
mockApi(config);
|
mockApi(config);
|
||||||
const { app } = require("../../web/scripts/app");
|
const { app } = require("../../web/scripts/app");
|
||||||
config.preSetup?.(app);
|
config.preSetup?.(app);
|
||||||
@ -21,6 +35,12 @@ export async function start(config = {}) {
|
|||||||
return { ...Ez.graph(app, global["LiteGraph"], global["LGraphCanvas"]), app };
|
return { ...Ez.graph(app, global["LiteGraph"], global["LGraphCanvas"]), app };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockUtils() {
|
||||||
|
jest.mock("../../web/scripts/utils", () => ({
|
||||||
|
addStylesheet: () => Promise.resolve()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param { ReturnType<Ez["graph"]>["graph"] } graph
|
* @param { ReturnType<Ez["graph"]>["graph"] } graph
|
||||||
* @param { (hasReloaded: boolean) => (Promise<void> | void) } cb
|
* @param { (hasReloaded: boolean) => (Promise<void> | void) } cb
|
||||||
|
|||||||
@ -18,16 +18,21 @@ function* walkSync(dir) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {
|
* @param {{
|
||||||
* {
|
|
||||||
* mockExtensions?: string[],
|
* mockExtensions?: string[],
|
||||||
* mockNodeDefs?: Record<string, ComfyObjectInfo>,
|
* mockNodeDefs?: Record<string, ComfyObjectInfo>,
|
||||||
* users?: boolean | Record<string, string>
|
* users?: boolean | Record<string, string>
|
||||||
* settings?: Record<string, string>
|
* settings?: Record<string, string>
|
||||||
* userData?: Record<string, any>
|
* userData?: Record<string, any>
|
||||||
* } } config
|
* }} config
|
||||||
*/
|
*/
|
||||||
export function mockApi({ mockExtensions, mockNodeDefs, users, settings, userData } = {}) {
|
export function mockApi(config = {}) {
|
||||||
|
let { mockExtensions, mockNodeDefs, users, settings, userData } = {
|
||||||
|
users: true,
|
||||||
|
settings: {},
|
||||||
|
userData: {},
|
||||||
|
...config,
|
||||||
|
};
|
||||||
if (!mockExtensions) {
|
if (!mockExtensions) {
|
||||||
mockExtensions = Array.from(walkSync(path.resolve("../web/extensions/core")))
|
mockExtensions = Array.from(walkSync(path.resolve("../web/extensions/core")))
|
||||||
.filter((x) => x.endsWith(".js"))
|
.filter((x) => x.endsWith(".js"))
|
||||||
@ -36,15 +41,6 @@ export function mockApi({ mockExtensions, mockNodeDefs, users, settings, userDat
|
|||||||
if (!mockNodeDefs) {
|
if (!mockNodeDefs) {
|
||||||
mockNodeDefs = JSON.parse(fs.readFileSync(path.resolve("./data/object_info.json")));
|
mockNodeDefs = JSON.parse(fs.readFileSync(path.resolve("./data/object_info.json")));
|
||||||
}
|
}
|
||||||
if(!users) {
|
|
||||||
users = true;
|
|
||||||
}
|
|
||||||
if(!settings) {
|
|
||||||
settings = {};
|
|
||||||
}
|
|
||||||
if(!userData) {
|
|
||||||
userData = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = new EventTarget();
|
const events = new EventTarget();
|
||||||
const mockApi = {
|
const mockApi = {
|
||||||
@ -56,15 +52,26 @@ export function mockApi({ mockExtensions, mockNodeDefs, users, settings, userDat
|
|||||||
getNodeDefs: jest.fn(() => mockNodeDefs),
|
getNodeDefs: jest.fn(() => mockNodeDefs),
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
apiURL: jest.fn((x) => "../../web/" + x),
|
apiURL: jest.fn((x) => "../../web/" + x),
|
||||||
|
createUser: jest.fn((username) => {
|
||||||
|
if(username in users) {
|
||||||
|
return { status: 400, json: () => "Duplicate" }
|
||||||
|
}
|
||||||
|
users[username + "!"] = username;
|
||||||
|
return { status: 200, json: () => username + "!" }
|
||||||
|
}),
|
||||||
getUsers: jest.fn(() => users),
|
getUsers: jest.fn(() => users),
|
||||||
getSettings: jest.fn(() => settings ?? {}),
|
getSettings: jest.fn(() => settings),
|
||||||
getUserData: jest.fn(f => {
|
storeSettings: jest.fn((v) => Object.assign(settings, v)),
|
||||||
if(f in userData) {
|
getUserData: jest.fn((f) => {
|
||||||
|
if (f in userData) {
|
||||||
return { status: 200, json: () => userData[f] };
|
return { status: 200, json: () => userData[f] };
|
||||||
} else {
|
} else {
|
||||||
return { status: 404 }
|
return { status: 404 };
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
storeUserData: jest.fn((file, data) => {
|
||||||
|
userData[file] = data;
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
jest.mock("../../web/scripts/api", () => ({
|
jest.mock("../../web/scripts/api", () => ({
|
||||||
get api() {
|
get api() {
|
||||||
|
|||||||
@ -324,13 +324,28 @@ class ComfyApi extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all setting values for the current user
|
* Gets a list of users or true if single user mode and default user is created, or false if single user mode and default user is not created.
|
||||||
* @returns { Promise<string, unknown> } A dictionary of id -> value
|
* @returns { Promise<string, unknown> | boolean } If multi-user, a dictionary of id -> value, else whether the default user is created
|
||||||
*/
|
*/
|
||||||
async getUsers() {
|
async getUsers() {
|
||||||
return (await this.fetchApi("/users")).json();
|
return (await this.fetchApi("/users")).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new user
|
||||||
|
* @param { string } username
|
||||||
|
* @returns The fetch response
|
||||||
|
*/
|
||||||
|
createUser(username) {
|
||||||
|
return this.fetchApi("/users", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all setting values for the current user
|
* Gets all setting values for the current user
|
||||||
* @returns { Promise<string, unknown> } A dictionary of id -> value
|
* @returns { Promise<string, unknown> } A dictionary of id -> value
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export class UserSelectionScreen {
|
|||||||
await addStylesheet(import.meta.url);
|
await addStylesheet(import.meta.url);
|
||||||
const userSelection = document.getElementById("comfy-user-selection");
|
const userSelection = document.getElementById("comfy-user-selection");
|
||||||
userSelection.style.display = "";
|
userSelection.style.display = "";
|
||||||
return new Promise(async (r) => {
|
return new Promise((resolve) => {
|
||||||
const input = userSelection.getElementsByTagName("input")[0];
|
const input = userSelection.getElementsByTagName("input")[0];
|
||||||
const select = userSelection.getElementsByTagName("select")[0];
|
const select = userSelection.getElementsByTagName("select")[0];
|
||||||
const inputSection = input.closest("section");
|
const inputSection = input.closest("section");
|
||||||
@ -40,7 +40,6 @@ export class UserSelectionScreen {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (inputActive == null) {
|
if (inputActive == null) {
|
||||||
error.textContent = "Please enter a username or select an existing user.";
|
error.textContent = "Please enter a username or select an existing user.";
|
||||||
return;
|
|
||||||
} else if (inputActive) {
|
} else if (inputActive) {
|
||||||
const username = input.value.trim();
|
const username = input.value.trim();
|
||||||
if (!username) {
|
if (!username) {
|
||||||
@ -53,13 +52,7 @@ export class UserSelectionScreen {
|
|||||||
const spinner = createSpinner();
|
const spinner = createSpinner();
|
||||||
button.prepend(spinner);
|
button.prepend(spinner);
|
||||||
try {
|
try {
|
||||||
const resp = await api.fetchApi("/users", {
|
const resp = await api.createUser(username);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username }),
|
|
||||||
});
|
|
||||||
if (resp.status >= 300) {
|
if (resp.status >= 300) {
|
||||||
let message = "Error creating user: " + resp.status + " " + resp.statusText;
|
let message = "Error creating user: " + resp.status + " " + resp.statusText;
|
||||||
try {
|
try {
|
||||||
@ -72,7 +65,7 @@ export class UserSelectionScreen {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
r({ username, userId: await resp.json(), created: true });
|
resolve({ username, userId: await resp.json(), created: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
spinner.remove();
|
spinner.remove();
|
||||||
error.textContent = err.message ?? err.statusText ?? err ?? "An unknown error occurred.";
|
error.textContent = err.message ?? err.statusText ?? err ?? "An unknown error occurred.";
|
||||||
@ -83,7 +76,7 @@ export class UserSelectionScreen {
|
|||||||
error.textContent = "Please select an existing user.";
|
error.textContent = "Please select an existing user.";
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
r({ username: users[select.value], userId: select.value, created: false });
|
resolve({ username: users[select.value], userId: select.value, created: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,7 +99,7 @@ export class UserSelectionScreen {
|
|||||||
select.style.color = "var(--descrip-text)";
|
select.style.color = "var(--descrip-text)";
|
||||||
|
|
||||||
if (select.value) {
|
if (select.value) {
|
||||||
// Focus the input, do this separately as sometimes browsers like to fill in the value
|
// Focus the select, do this separately as sometimes browsers like to fill in the value
|
||||||
select.focus();
|
select.focus();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user