feat: add view-as-role feature for site admins (#15)
Site admins can now temporarily view the site as a player, tournament admin, or club admin to understand the experience for each role. Changes: - Added RoleSwitcher context and provider for client-side role simulation - Added role switcher dropdown in Navigation (visible only to site admins) - Added yellow banner showing current viewing-as role with reset button - Navigation links and wordmark href now use effective role for conditional display - Added BDD feature file with 5 scenarios covering all role transitions - Added step definitions for site admin login and role switcher interactions The view-as feature is purely UI-level - server-side permissions remain unchanged. 5 scenarios, 27 steps, all passing.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
Feature: View As Role
|
||||
As a site admin
|
||||
I want to temporarily view the site as a player or club admin
|
||||
So that I can understand and improve the experience for each role
|
||||
|
||||
@happy-path @admin-features @issue-15
|
||||
Scenario: Site admin sees role switcher in navigation
|
||||
Given I am logged in as a site admin
|
||||
When I view the navigation
|
||||
Then I should see the role switcher dropdown
|
||||
Then the role switcher should default to "Viewing as Site Admin"
|
||||
|
||||
@happy-path @admin-features @issue-15
|
||||
Scenario: Site admin switches to player view
|
||||
Given I am logged in as a site admin
|
||||
When I select "View as Player" from the role switcher
|
||||
Then I should see the player navigation links
|
||||
And I should not see the "Admin" link
|
||||
And I should not see the "Users" link
|
||||
And I should see a banner indicating I am viewing as "Player"
|
||||
|
||||
@happy-path @admin-features @issue-15
|
||||
Scenario: Site admin switches to tournament admin view
|
||||
Given I am logged in as a site admin
|
||||
When I select "View as Tournament Admin" from the role switcher
|
||||
Then I should see the "Tournaments" link
|
||||
And I should not see the "Admin" link
|
||||
And I should not see the "Users" link
|
||||
And I should see a banner indicating I am viewing as "Tournament Admin"
|
||||
|
||||
@happy-path @admin-features @issue-15
|
||||
Scenario: Site admin switches to club admin view
|
||||
Given I am logged in as a site admin
|
||||
When I select "View as Club Admin" from the role switcher
|
||||
Then I should see the "Admin" link
|
||||
And I should see the "Users" link
|
||||
And I should see a banner indicating I am viewing as "Club Admin"
|
||||
|
||||
@happy-path @admin-features @issue-15
|
||||
Scenario: Site admin resets to site admin view
|
||||
Given I am logged in as a site admin
|
||||
When I select "View as Player" from the role switcher
|
||||
And I click the "Reset to Site Admin" button
|
||||
Then the role switcher should default to "Viewing as Site Admin"
|
||||
And I should see the "Admin" link
|
||||
And I should not see the viewing as banner
|
||||
@@ -158,6 +158,58 @@ Given('I am logged in as a tournament admin', async function () {
|
||||
console.log(`🌍 User created: ${credentials.email}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Precondition: I am logged in as a site admin
|
||||
* Creates a new user and assigns site_admin role via Prisma
|
||||
*/
|
||||
Given('I am logged in as a site admin', async function () {
|
||||
console.log('🌍 Creating and logging in as a site admin...');
|
||||
|
||||
const credentials = generateTestCredentials();
|
||||
world.user = credentials;
|
||||
|
||||
await world.page.goto(`${world.baseURL}/auth/register`);
|
||||
await world.page.waitForLoadState('domcontentloaded');
|
||||
|
||||
await world.page.fill('input[name="name"]', credentials.name);
|
||||
await world.page.fill('input[name="email"]', credentials.email);
|
||||
await world.page.fill('input[name="password"]', credentials.password);
|
||||
|
||||
await world.page.click('button[type="submit"]');
|
||||
await world.page.waitForURL(/\/players\/\d+\/profile/, { timeout: 15000 });
|
||||
|
||||
const currentUrl = world.page.url();
|
||||
const match = currentUrl.match(/\/players\/(\d+)\/profile/);
|
||||
if (match) {
|
||||
const playerId = match[1];
|
||||
world.playerId = playerId;
|
||||
|
||||
const prisma = await world.getPrisma();
|
||||
const player = await prisma.player.findUnique({
|
||||
where: { id: parseInt(playerId) },
|
||||
include: { user: true }
|
||||
});
|
||||
|
||||
if (player && player.user) {
|
||||
const userId = player.user.id;
|
||||
(world.user as any).id = userId;
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { role: 'site_admin' }
|
||||
});
|
||||
console.log(`🌍 Assigned site_admin role to user: ${userId}`);
|
||||
|
||||
// Navigate to home page to trigger Navigation re-mount with new role
|
||||
await world.page.goto(`${world.baseURL}/`);
|
||||
await world.page.waitForLoadState('networkidle');
|
||||
await world.page.waitForTimeout(1000);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🌍 Site admin created: ${credentials.email}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Precondition: I am logged in as a club admin
|
||||
* Uses a pre-existing admin user from the database
|
||||
|
||||
@@ -690,3 +690,61 @@ Then('I should be on the match result entry page', async function () {
|
||||
console.log(`🌍 Checking current URL: ${currentUrl}`);
|
||||
expect(currentUrl).toMatch(/\/matches\/|\/admin\/tournaments\/\d+\/(entry|results)/);
|
||||
});
|
||||
|
||||
// View As Role Steps
|
||||
When('I view the navigation', async function () {
|
||||
await world.page.waitForLoadState('networkidle');
|
||||
await world.page.waitForTimeout(1000);
|
||||
console.log('🌍 Viewing navigation');
|
||||
});
|
||||
|
||||
Then('I should see the role switcher dropdown', async function () {
|
||||
const switcher = world.page.locator('[data-testid="role-switcher"]');
|
||||
await expect(switcher).toBeVisible({ timeout: 5000 });
|
||||
console.log('🌍 Verified role switcher dropdown is visible');
|
||||
});
|
||||
|
||||
Then('the role switcher should default to {string}', async function (expectedText: string) {
|
||||
const switcher = world.page.locator('[data-testid="role-switcher"]');
|
||||
const selectedValue = await switcher.inputValue();
|
||||
const selectedText = await switcher.locator('option:checked').textContent();
|
||||
console.log(`🌍 Dropdown selected text: "${selectedText}", value: "${selectedValue}"`);
|
||||
expect(selectedText?.trim()).toBe(expectedText);
|
||||
});
|
||||
|
||||
When('I select {string} from the role switcher', async function (optionText: string) {
|
||||
const switcher = world.page.locator('[data-testid="role-switcher"]');
|
||||
await switcher.selectOption({ label: optionText });
|
||||
await world.page.waitForTimeout(500);
|
||||
console.log(`🌍 Selected "${optionText}" from role switcher`);
|
||||
});
|
||||
|
||||
Then('I should see the player navigation links', async function () {
|
||||
await expect(world.page.locator('nav a:has-text("Rankings")')).toBeVisible();
|
||||
await expect(world.page.locator('nav a:has-text("Tournaments")')).toBeVisible();
|
||||
console.log('🌍 Verified player navigation links are visible');
|
||||
});
|
||||
|
||||
Then('I should not see the {string} link', async function (linkText: string) {
|
||||
const link = world.page.locator(`nav a:has-text("${linkText}")`);
|
||||
await expect(link).not.toBeVisible({ timeout: 3000 });
|
||||
console.log(`🌍 Verified "${linkText}" nav link is not visible`);
|
||||
});
|
||||
|
||||
Then('I should see the {string} link', async function (linkText: string) {
|
||||
const link = world.page.locator(`nav a:has-text("${linkText}")`);
|
||||
await expect(link).toBeVisible({ timeout: 5000 });
|
||||
console.log(`🌍 Verified "${linkText}" nav link is visible`);
|
||||
});
|
||||
|
||||
Then('I should see a banner indicating I am viewing as {string}', async function (roleName: string) {
|
||||
const banner = world.page.locator(`text=Viewing as ${roleName}`);
|
||||
await expect(banner).toBeVisible({ timeout: 5000 });
|
||||
console.log(`🌍 Verified viewing as ${roleName} banner is visible`);
|
||||
});
|
||||
|
||||
Then('I should not see the viewing as banner', async function () {
|
||||
const banner = world.page.locator('[data-testid="reset-view-as"]');
|
||||
await expect(banner).not.toBeVisible({ timeout: 3000 });
|
||||
console.log('🌍 Verified viewing as banner is not visible');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user