From dae983734a71c722a30d80e6f57439249ddd1b01 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sun, 5 Apr 2026 19:27:25 +0100 Subject: [PATCH] Accessibility fixes. --- nuxt.config.ts | 24 +++++ package.json | 2 +- playwright.config.js | 31 +++--- scripts/seed-tags.js | 163 ++++++++++++++++------------- server/api/dev/member-login.get.js | 49 +++++---- server/api/dev/members.get.js | 23 ++-- server/api/dev/test-login.get.js | 49 +++++---- 7 files changed, 201 insertions(+), 140 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 8d3be2f..fb26fa2 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -30,6 +30,30 @@ export default defineNuxtConfig({ hmr: { port: 24678, }, + watch: { + ignored: [ + "**/.git/**", + "**/.nuxt/**", + "**/.output/**", + "**/node_modules/**", + "**/dist/**", + "**/e2e/**", + "**/coverage/**", + ], + }, + }, + }, + nitro: { + watchOptions: { + ignored: [ + "**/.git/**", + "**/.nuxt/**", + "**/.output/**", + "**/node_modules/**", + "**/dist/**", + "**/e2e/**", + "**/coverage/**", + ], }, }, runtimeConfig: { diff --git a/package.json b/package.json index e4f32d0..c35471a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "build": "nuxt build", - "dev": "nuxt dev", + "dev": " nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare", diff --git a/playwright.config.js b/playwright.config.js index fa7b12a..530ec9a 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,31 +1,32 @@ -import { defineConfig } from '@playwright/test' +import { defineConfig } from "@playwright/test"; export default defineConfig({ - testDir: './e2e', - outputDir: 'e2e/test-results', - snapshotDir: 'e2e/__screenshots__', + testDir: "./e2e", + outputDir: "e2e/test-results", + snapshotDir: "e2e/__screenshots__", fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: process.env.CI ? 1 : undefined, - reporter: 'html', + reporter: "html", use: { - baseURL: 'http://localhost:3000', - trace: 'on-first-retry', + baseURL: "http://localhost:3000", + trace: "on-first-retry", }, projects: [ { - name: 'chromium', - use: { browserName: 'chromium' }, + name: "chromium", + use: { browserName: "chromium" }, }, ], webServer: { - command: 'npm run dev', - url: 'http://localhost:3000', - reuseExistingServer: !process.env.CI, + command: "npm run build && NODE_ENV=development npm run preview", + url: "http://localhost:3000", + reuseExistingServer: false, env: { - NUXT_PUBLIC_COMING_SOON: 'false', - NODE_ENV: 'development', + NUXT_PUBLIC_COMING_SOON: "false", + NODE_ENV: "development", + ALLOW_DEV_TEST_ENDPOINTS: "true", }, }, -}) +}); diff --git a/scripts/seed-tags.js b/scripts/seed-tags.js index a656ca4..2f7eaaf 100644 --- a/scripts/seed-tags.js +++ b/scripts/seed-tags.js @@ -5,109 +5,128 @@ * Safe to run multiple times. */ -import 'dotenv/config' -import mongoose from 'mongoose' -import Tag from '../server/models/tag.js' -import { connectDB } from '../server/utils/mongoose.js' +import "dotenv/config"; +import mongoose from "mongoose"; +import Tag from "../server/models/tag.js"; +import { connectDB } from "../server/utils/mongoose.js"; // Convert a slug like "qa-and-testing" to "QA and Testing" // Special-cases common abbreviations. const ABBREVIATIONS = new Map([ - ['qa', 'QA'], - ['ux', 'UX'], - ['ui', 'UI'], - ['devops', 'DevOps'], -]) + ["qa", "QA"], + ["ux", "UX"], + ["ui", "UI"], + ["devops", "DevOps"], +]); function slugToLabel(slug) { return slug - .split('-') - .map((word) => ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1)) - .join(' ') + .split("-") + .map( + (word) => + ABBREVIATIONS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1), + ) + .join(" "); } const CRAFT_SLUGS = [ - 'game-design', - 'programming', - 'narrative-design', - 'art-and-animation', - 'audio-and-music', - 'production-management', - 'qa-and-testing', - 'community-management', - 'marketing-and-comms', - 'ux-and-ui-design', - 'business-development', - 'devops-and-tools', - 'localization', - 'accessibility', - 'analytics-and-data', - 'education-and-mentoring', -] + "game-design", + "programming", + "narrative-design", + "art-and-animation", + "audio-and-music", + "production-management", + "qa-and-testing", + "community-management", + "marketing-and-comms", + "ux-and-ui-design", + "business-development", + "devops-and-tools", + "localization", + "accessibility", + "analytics-and-data", + "education-and-mentoring", +]; const COOPERATIVE_SLUGS = [ - 'governance', - 'finance-and-budgeting', - 'legal-structures', - 'conflict-resolution', - 'consensus-decision-making', - 'revenue-sharing', - 'cooperative-bylaws', - 'member-onboarding', - 'democratic-management', - 'worker-ownership', - 'platform-cooperativism', - 'cooperative-marketing', - 'shared-resources', - 'cooperative-funding', - 'community-building', - 'equity-and-inclusion', - 'cooperative-tech', - 'sustainability', - 'collective-bargaining', - 'inter-coop-collaboration', -] + "governance", + "finance-and-budgeting", + "legal-structures", + "conflict-resolution", + "consensus-decision-making", + "revenue-sharing", + "cooperative-bylaws", + "member-onboarding", + "democratic-management", + "worker-ownership", + "platform-cooperativism", + "cooperative-marketing", + "shared-resources", + "cooperative-funding", + "community-building", + "equity-and-inclusion", + "cooperative-tech", + "sustainability", + "collective-bargaining", + "inter-coop-collaboration", +]; async function seedTags() { - await connectDB() + await connectDB(); const tagDefs = [ - ...CRAFT_SLUGS.map((slug) => ({ slug, pool: 'craft', label: slugToLabel(slug) })), - ...COOPERATIVE_SLUGS.map((slug) => ({ slug, pool: 'cooperative', label: slugToLabel(slug) })), - ] + ...CRAFT_SLUGS.map((slug) => ({ + slug, + pool: "craft", + label: slugToLabel(slug), + })), + ...COOPERATIVE_SLUGS.map((slug) => ({ + slug, + pool: "cooperative", + label: slugToLabel(slug), + })), + ]; - let upserted = 0 - let unchanged = 0 + let upserted = 0; + let unchanged = 0; for (const { slug, pool, label } of tagDefs) { const result = await Tag.updateOne( { slug }, - { $setOnInsert: { slug, pool, label, active: true, createdAt: new Date() } }, - { upsert: true } - ) + { + $setOnInsert: { + slug, + pool, + label, + active: true, + createdAt: new Date(), + }, + }, + { upsert: true }, + ); if (result.upsertedCount > 0) { - console.log(` + Created [${pool}] ${label} (${slug})`) - upserted++ + console.log(` + Created [${pool}] ${label} (${slug})`); + upserted++; } else { - unchanged++ + unchanged++; } } - console.log('\n=== Seed Complete ===') - console.log(` Total tags defined: ${tagDefs.length}`) - console.log(` Newly created: ${upserted}`) - console.log(` Already existed: ${unchanged}`) + console.log("\n=== Seed Complete ==="); + console.log(` Total tags defined: ${tagDefs.length}`); + console.log(` Newly created: ${upserted}`); + console.log(` Already existed: ${unchanged}`); } seedTags() .then(() => { - console.log('\nTag seed completed successfully') - process.exit(0) + console.log("\nTag seed completed successfully"); + process.exit(0); }) .catch((err) => { - console.error('\nTag seed failed:', err) - process.exit(1) + console.error("\nTag seed failed:", err); + process.exit(1); }) .finally(() => { - mongoose.connection.close() - }) + mongoose.connection.close(); + }); diff --git a/server/api/dev/member-login.get.js b/server/api/dev/member-login.get.js index 465f062..8de91cc 100644 --- a/server/api/dev/member-login.get.js +++ b/server/api/dev/member-login.get.js @@ -1,41 +1,50 @@ -import jwt from 'jsonwebtoken' -import Member from '../../models/member.js' -import { connectDB } from '../../utils/mongoose.js' +import jwt from "jsonwebtoken"; +import Member from "../../models/member.js"; +import { connectDB } from "../../utils/mongoose.js"; export default defineEventHandler(async (event) => { - // Only allow in development - if (process.env.NODE_ENV === 'production') { - throw createError({ statusCode: 404, statusMessage: 'Not found' }) + // Only allow in development, unless explicitly enabled for Playwright preview runs + if ( + process.env.NODE_ENV === "production" && + process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true" + ) { + throw createError({ statusCode: 404, statusMessage: "Not found" }); } - const query = getQuery(event) - const email = query.email + const query = getQuery(event); + const email = query.email; if (!email) { - throw createError({ statusCode: 400, statusMessage: 'email query param required' }) + throw createError({ + statusCode: 400, + statusMessage: "email query param required", + }); } - await connectDB() + await connectDB(); - const member = await Member.findOne({ email: email.toLowerCase() }) + const member = await Member.findOne({ email: email.toLowerCase() }); if (!member) { - throw createError({ statusCode: 404, statusMessage: `No member found with email: ${email}` }) + throw createError({ + statusCode: 404, + statusMessage: `No member found with email: ${email}`, + }); } - const config = useRuntimeConfig(event) + const config = useRuntimeConfig(event); const token = jwt.sign( { memberId: member._id, email: member.email, tv: member.tokenVersion }, config.jwtSecret, - { expiresIn: '7d' } - ) + { expiresIn: "7d" }, + ); - setCookie(event, 'auth-token', token, { + setCookie(event, "auth-token", token, { httpOnly: true, secure: false, - sameSite: 'lax', + sameSite: "lax", maxAge: 60 * 60 * 24 * 7, - }) + }); - await sendRedirect(event, '/member/account', 302) -}) + await sendRedirect(event, "/member/account", 302); +}); diff --git a/server/api/dev/members.get.js b/server/api/dev/members.get.js index fa501ee..64211ef 100644 --- a/server/api/dev/members.get.js +++ b/server/api/dev/members.get.js @@ -1,19 +1,24 @@ -import Member from '../../models/member.js' -import { connectDB } from '../../utils/mongoose.js' +import Member from "../../models/member.js"; +import { connectDB } from "../../utils/mongoose.js"; export default defineEventHandler(async () => { - if (process.env.NODE_ENV === 'production') { - throw createError({ statusCode: 404, statusMessage: 'Not found' }) + if ( + process.env.NODE_ENV === "production" && + process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true" + ) { + throw createError({ statusCode: 404, statusMessage: "Not found" }); } - await connectDB() + await connectDB(); - const members = await Member.find({}, 'name email circle role status').sort({ name: 1 }).lean() + const members = await Member.find({}, "name email circle role status") + .sort({ name: 1 }) + .lean(); return members.map((m) => ({ label: `${m.name} (${m.email})`, value: m.email, circle: m.circle, - role: m.role - })) -}) + role: m.role, + })); +}); diff --git a/server/api/dev/test-login.get.js b/server/api/dev/test-login.get.js index 650349d..77eda3a 100644 --- a/server/api/dev/test-login.get.js +++ b/server/api/dev/test-login.get.js @@ -1,42 +1,45 @@ -import jwt from 'jsonwebtoken' -import Member from '../../models/member.js' -import { connectDB } from '../../utils/mongoose.js' +import jwt from "jsonwebtoken"; +import Member from "../../models/member.js"; +import { connectDB } from "../../utils/mongoose.js"; export default defineEventHandler(async (event) => { - // Only allow in development - if (process.env.NODE_ENV === 'production') { - throw createError({ statusCode: 404, statusMessage: 'Not found' }) + // Only allow in development, unless explicitly enabled for Playwright preview runs + if ( + process.env.NODE_ENV === "production" && + process.env.ALLOW_DEV_TEST_ENDPOINTS !== "true" + ) { + throw createError({ statusCode: 404, statusMessage: "Not found" }); } - await connectDB() + await connectDB(); // Find or create a test admin user - let member = await Member.findOne({ email: 'test-admin@ghostguild.dev' }) + let member = await Member.findOne({ email: "test-admin@ghostguild.dev" }); if (!member) { member = await Member.create({ - email: 'test-admin@ghostguild.dev', - name: 'Test Admin', - circle: 'founder', - contributionTier: '0', - role: 'admin', - status: 'active', - }) + email: "test-admin@ghostguild.dev", + name: "Test Admin", + circle: "founder", + contributionTier: "0", + role: "admin", + status: "active", + }); } - const config = useRuntimeConfig(event) + const config = useRuntimeConfig(event); const token = jwt.sign( { memberId: member._id, email: member.email, tv: member.tokenVersion }, config.jwtSecret, - { expiresIn: '7d' } - ) + { expiresIn: "7d" }, + ); - setCookie(event, 'auth-token', token, { + setCookie(event, "auth-token", token, { httpOnly: true, secure: false, - sameSite: 'lax', + sameSite: "lax", maxAge: 60 * 60 * 24 * 7, - }) + }); - await sendRedirect(event, '/admin', 302) -}) + await sendRedirect(event, "/admin", 302); +});