[Closes #8] Added authentication
Some checks failed
Production Build and Deploy / Build (push) Failing after 44s
Production Build and Deploy / Deploy (push) Has been skipped

This commit is contained in:
Liviu Burcusel 2026-01-07 11:11:35 +01:00
parent 6eefa137bb
commit 97211cdccd
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
65 changed files with 5831 additions and 440 deletions

View file

@ -0,0 +1,88 @@
import { mount, flushPromises } from "@vue/test-utils";
import { describe, it, expect, vi, beforeEach } from "vitest";
import CreateAccountPage from "~/pages/member/auth/create-account.vue";
// Mock auth client
const authMocks = vi.hoisted(() => ({
signUpEmail: vi.fn(),
}));
vi.mock("~~/shared/utils/auth-client", () => ({
authClient: {
signUp: {
email: authMocks.signUpEmail,
},
},
}));
// Mock UI components
vi.mock("@/components/ui/button", () => ({
Button: { template: "<button><slot /></button>" },
}));
vi.mock("@/components/ui/input", () => ({
Input: {
props: ["modelValue", "id", "type"],
emits: ["update:modelValue"],
template: `<input :id="id" :type="type" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />`,
},
}));
vi.mock("@/components/ui/card", () => ({
Card: { template: "<div><slot /></div>" },
CardHeader: { template: "<div><slot /></div>" },
CardTitle: { template: "<h1><slot /></h1>" },
CardDescription: { template: "<p><slot /></p>" },
CardContent: { template: "<div><slot /></div>" },
}));
vi.mock("@/components/ui/field", () => ({
Field: { template: "<div><slot /></div>" },
FieldGroup: { template: "<div><slot /></div>" },
FieldLabel: { template: "<label><slot /></label>" },
FieldDescription: { template: "<span><slot /></span>" },
}));
describe("CreateAccountPage", () => {
beforeEach(() => {
vi.clearAllMocks();
authMocks.signUpEmail.mockResolvedValue({ data: {}, error: null });
});
it("renders the signup form correctly", async () => {
const wrapper = mount(CreateAccountPage);
// Wait for any async rendering if necessary
await flushPromises();
expect(wrapper.text()).toContain("Create your account");
expect(wrapper.find('input[type="email"]').exists()).toBe(true);
expect(wrapper.find('input[type="password"]').exists()).toBe(true);
});
it("submits the form with correct data", async () => {
const wrapper = mount(CreateAccountPage);
await flushPromises();
// Fill the form
const nameInput = wrapper.find('input[id="name"]');
const emailInput = wrapper.find('input[id="email"]');
const passwordInput = wrapper.find('input[id="password"]');
const confirmPasswordInput = wrapper.find('input[id="confirm-password"]');
await nameInput.setValue("Test User");
await emailInput.setValue("test@example.com");
await passwordInput.setValue("password123");
await confirmPasswordInput.setValue("password123");
// Submit
await wrapper.find("form").trigger("submit");
expect(authMocks.signUpEmail).toHaveBeenCalledWith({
name: "Test User",
email: "test@example.com",
password: "password123", // NOSONAR - Mocked value
});
});
});

View file

@ -0,0 +1,143 @@
import { mount, flushPromises } from "@vue/test-utils";
import { describe, expect, it, vi, beforeEach, beforeAll, afterAll } from "vitest";
import LoginPage from "~/pages/member/auth/login.vue";
// Mock the auth store
const authStoreMocks = vi.hoisted(() => ({
signIn: vi.fn(),
lastError: null,
}));
vi.mock("~/stores/auth", () => ({
useAuthStore: () => authStoreMocks,
}));
// Make useAuthStore available globally for the component
globalThis.useAuthStore = () => authStoreMocks;
// Mock UI components
vi.mock("@/components/ui/button", () => ({
Button: {
props: ["variant", "type"],
template: '<button :type="type"><slot /></button>',
},
}));
vi.mock("@/components/ui/input", () => ({
Input: {
props: ["modelValue", "id", "type", "placeholder", "required"],
emits: ["update:modelValue"],
template: `<input :id="id" :type="type" :placeholder="placeholder" :required="required" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />`,
},
}));
vi.mock("@/components/ui/card", () => ({
Card: { template: "<div><slot /></div>" },
CardHeader: { template: "<div><slot /></div>" },
CardTitle: { template: "<h1><slot /></h1>" },
CardDescription: { template: "<p><slot /></p>" },
CardContent: { template: "<div><slot /></div>" },
}));
vi.mock("@/components/ui/field", () => ({
Field: {
props: ["variant"],
template: "<div><slot /></div>",
},
FieldGroup: { template: "<div><slot /></div>" },
FieldLabel: { template: "<label><slot /></label>" },
FieldDescription: { template: "<span><slot /></span>" },
}));
vi.mock("lucide-vue-next", () => ({
Frown: { template: "<svg></svg>" },
}));
describe("LoginPage", () => {
beforeAll(() => {
const shouldSuppress = (args: string[]) => {
const msg = args.join(" ");
return msg.includes("<Suspense> is an experimental feature");
};
const spyMethods = ["warn", "error", "log", "info"] as const;
for (const method of spyMethods) {
const original = console[method];
vi.spyOn(console, method).mockImplementation((...args) => {
if (shouldSuppress(args)) return;
original(...args);
});
}
});
afterAll(() => {
vi.restoreAllMocks();
});
beforeEach(() => {
vi.clearAllMocks();
authStoreMocks.lastError = null;
});
it("renders the login form correctly", async () => {
const wrapper = mount({
components: { LoginPage },
template: "<Suspense><LoginPage /></Suspense>",
});
await flushPromises();
expect(wrapper.text()).toContain("Login");
expect(wrapper.text()).toContain("Enter your email below to login");
expect(wrapper.find('input[type="email"]').exists()).toBe(true);
expect(wrapper.find('input[type="password"]').exists()).toBe(true);
expect(wrapper.text()).toContain("Don't have an account?");
expect(wrapper.text()).toContain("Create account");
});
it("submits the form with correct credentials", async () => {
const wrapper = mount({
components: { LoginPage },
template: "<Suspense><LoginPage /></Suspense>",
});
await flushPromises();
// Fill the form
const emailInput = wrapper.find('input[id="email"]');
const passwordInput = wrapper.find('input[id="password"]');
await emailInput.setValue("test@example.com");
await passwordInput.setValue("password123");
// Submit the form
await wrapper.find("form").trigger("submit");
expect(authStoreMocks.signIn).toHaveBeenCalledWith("test@example.com", "password123");
});
it("displays error message when lastError is set", async () => {
authStoreMocks.lastError = "Invalid credentials";
const wrapper = mount({
components: { LoginPage },
template: "<Suspense><LoginPage /></Suspense>",
});
await flushPromises();
expect(wrapper.text()).toContain("Invalid credentials");
});
it("contains links to Terms of Service and Privacy Policy", async () => {
const wrapper = mount({
components: { LoginPage },
template: "<Suspense><LoginPage /></Suspense>",
});
await flushPromises();
expect(wrapper.text()).toContain("Terms of Service");
expect(wrapper.text()).toContain("Privacy Policy");
});
});

View file

@ -0,0 +1,91 @@
import { mount, flushPromises } from "@vue/test-utils";
import { describe, expect, it, vi, beforeEach, beforeAll, afterAll } from "vitest";
import LogoutPage from "~/pages/member/auth/logout.vue";
// Mock the auth store
const mocks = vi.hoisted(() => ({
init: vi.fn(),
signOut: vi.fn(),
}));
vi.mock("~/stores/auth", () => ({
useAuthStore: () => ({
init: mocks.init,
signOut: mocks.signOut,
}),
}));
// Mock UI components
vi.mock("@/components/ui/button", () => ({
Button: {
template: "<button><slot /></button>",
},
}));
vi.mock("@/components/ui/card", () => ({
Card: { template: "<div><slot /></div>" },
CardContent: { template: "<div><slot /></div>" },
CardDescription: { template: "<div><slot /></div>" },
CardHeader: { template: "<div><slot /></div>" },
CardTitle: { template: "<div><slot /></div>" },
}));
vi.mock("@/components/ui/field", () => ({
Field: { template: "<div><slot /></div>" },
FieldDescription: { template: "<div><slot /></div>" },
}));
describe("LogoutPage", () => {
beforeAll(() => {
const shouldSuppress = (args: string[]) => {
const msg = args.join(" ");
return msg.includes("<Suspense> is an experimental feature");
};
const spyMethods = ["warn", "error", "log", "info"] as const;
for (const method of spyMethods) {
const original = console[method];
vi.spyOn(console, method).mockImplementation((...args) => {
if (shouldSuppress(args)) return;
original(...args);
});
}
});
afterAll(() => {
vi.restoreAllMocks();
});
beforeEach(() => {
vi.clearAllMocks();
});
it("renders correctly and calls init", async () => {
const wrapper = mount({
components: { LogoutPage },
template: "<Suspense><LogoutPage /></Suspense>",
});
await flushPromises();
expect(mocks.init).toHaveBeenCalled();
expect(wrapper.text()).toContain("Logout");
expect(wrapper.text()).toContain("Are you sure you want to logout?");
expect(wrapper.text()).toContain("Home");
});
it("calls signOut on form submit", async () => {
const wrapper = mount({
components: { LogoutPage },
template: "<Suspense><LogoutPage /></Suspense>",
});
await flushPromises();
const form = wrapper.find("form");
expect(form.exists()).toBe(true);
await form.trigger("submit");
expect(mocks.signOut).toHaveBeenCalled();
});
});