Ghost/apps/admin-x-settings/test/acceptance/membership/recommendations.test.ts
Peter Zimon 3ef8b53fad
Added "No search result" screen to Settings (#19672)
refs.
https://linear.app/tryghost/issue/DES-21/empty-screen-is-missing-for-search-in-settings

- Search is one of the most useful functions in Settings and currently
the screen when there's no result for a searchterm is just a plain white
screen. Very non user-friendly.
- This update gives us an opportunity to improve the overall visual
hierarchy and focus of Settings in general.

---------

Co-authored-by: Ronald Langeveld <hi@ronaldlangeveld.com>
2024-02-08 08:32:40 +01:00

204 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {expect, test} from '@playwright/test';
import {globalDataRequests} from '../../utils/acceptance';
import {mockApi, responseFixtures, toggleLabsFlag} from '@tryghost/admin-x-framework/test/acceptance';
test.describe('Recommendations', async () => {
test.beforeEach(async () => {
toggleLabsFlag('recommendations', true);
});
test('can view recommendations', async ({page}) => {
await mockApi({page, requests: {
...globalDataRequests,
browseRecommendations: {method: 'GET', path: '/recommendations/?include=count.clicks%2Ccount.subscribers&order=created_at+desc&limit=5', response: responseFixtures.recommendations}
}});
await page.goto('/');
const section = page.getByTestId('recommendations');
const activeTab = section.locator('[role=tabpanel]:not(.hidden)');
await section.getByRole('tab', {name: 'Your Recommendations'}).click();
const recommendation1 = activeTab.getByTestId('recommendation-list-item').first();
const recommendation2 = activeTab.getByTestId('recommendation-list-item').last();
await expect(recommendation1).toContainText('Recommendation 1 title');
await expect(recommendation2).toContainText('Recommendation 2 title');
});
test('can add a recommendation', async ({page}) => {
const {lastApiRequests} = await mockApi({page, requests: {
...globalDataRequests,
checkRecommendation: {method: 'POST', path: '/recommendations/check/', response: {recommendations: [{url: '', one_click_subscribe: true}], meta: {}}},
addRecommendation: {method: 'POST', path: '/recommendations/', response: {}}
}});
await page.goto('/');
// Open add recommendation modal
const section = await page.getByTestId('recommendations');
await section.getByRole('button', {name: 'Add recommendation'}).click();
const modal = page.getByTestId('add-recommendation-modal');
// Screen 1 - URL
const url = modal.getByLabel('url');
// Validate errors
url.fill('not a real url');
await modal.getByRole('button', {name: 'Next'}).click();
await expect(modal).toContainText('Enter a valid URL');
// Validate success
modal.getByRole('textbox').fill('https://example.com/a-cool-website');
await modal.getByRole('button', {name: 'Next'}).click();
// Screen 2 — Title & description
const title = modal.getByLabel('Title');
const description = modal.getByLabel('Short description');
// Validate errors
await title.fill('');
await description.fill('This is a long description with more than 200 characters: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl.');
await expect(modal).toContainText('Max: 200 characters. Youve used 510');
// Validate success
await title.fill('This is a title');
await description.fill('This is a description');
await modal.getByRole('button', {name: 'Add'}).click();
expect(lastApiRequests.addRecommendation?.body).toEqual({
recommendations: [
{excerpt: null,
favicon: null,
featured_image: null,
one_click_subscribe: true,
description: 'This is a description',
title: 'This is a title',
url: 'https://example.com/a-cool-website'}
]
});
});
test('errors when adding an existing URL', async ({page}) => {
await mockApi({page, requests: {
...globalDataRequests,
checkRecommendation: {method: 'POST', path: '/recommendations/check/', response: {recommendations: [{url: 'https://recommendation1.com', one_click_subscribe: true, id: 'exists'}], meta: {}}}
}});
await page.goto('/');
const section = page.getByTestId('recommendations');
// Open add recommendation modal
await section.getByRole('button', {name: 'Add recommendation'}).click();
const modal = page.getByTestId('add-recommendation-modal');
// Add existing URL
modal.getByLabel('url').fill('https://recommendation1.com');
await modal.getByRole('button', {name: 'Next'}).click();
await expect(page.getByTestId('toast-error')).toHaveText(/A recommendation with this URL already exists/);
});
test('can edit a recommendation', async ({page}) => {
const recommendationToEdit = responseFixtures.recommendations.recommendations[0];
const {lastApiRequests} = await mockApi({page, requests: {
...globalDataRequests,
browseRecommendations: {method: 'GET', path: '/recommendations/?include=count.clicks%2Ccount.subscribers&order=created_at+desc&limit=5', response: responseFixtures.recommendations},
editRecommendation: {method: 'PUT', path: `/recommendations/${recommendationToEdit.id}/`, response: {}}
}});
await page.goto('/');
const section = await page.getByTestId('recommendations');
const activeTab = section.locator('[role=tabpanel]:not(.hidden)');
await section.getByRole('tab', {name: 'Your Recommendations'}).click();
// Open edit recommendation on the first recommendation
const recommendation1 = activeTab.getByTestId('recommendation-list-item').first();
await recommendation1.click();
const modal = page.getByTestId('edit-recommendation-modal');
const title = modal.getByLabel('Title');
const description = modal.getByLabel('Short description');
// Validate errors
await title.fill('');
await description.fill('This is a long description with more than 200 characters: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl. Donec auctor, nisl eget aliquam aliquet, nisl nunc aliquam nunc, quis aliquam nisl nunc eget nisl.');
await expect(modal).toContainText('Max: 200 characters. Youve used 510');
// Validate success
await modal.getByLabel('Title').fill('Updated title');
await modal.getByLabel('Short description').fill('Updated description');
await modal.getByRole('button', {name: 'Save'}).click();
expect(lastApiRequests.editRecommendation?.body).toEqual({
recommendations: [
{...recommendationToEdit,
description: 'Updated description',
title: 'Updated title'}
]
});
});
test('can delete a recommendation', async ({page}) => {
const recommendationToDelete = responseFixtures.recommendations.recommendations[0];
const {lastApiRequests} = await mockApi({page, requests: {
...globalDataRequests,
browseRecommendations: {method: 'GET', path: '/recommendations/?include=count.clicks%2Ccount.subscribers&order=created_at+desc&limit=5', response: responseFixtures.recommendations},
deleteRecommendation: {method: 'DELETE', path: `/recommendations/${recommendationToDelete.id}/`, response: {}}
}});
await page.goto('/');
const section = page.getByTestId('recommendations');
const activeTab = section.locator('[role=tabpanel]:not(.hidden)');
await section.getByRole('tab', {name: 'Your Recommendations'}).click();
// Open edit recommendation on the first recommendation
const recommendation1 = activeTab.getByTestId('recommendation-list-item').first();
await recommendation1.click();
// Click on delete
const modal = page.getByTestId('edit-recommendation-modal');
await modal.getByRole('button', {name: 'Delete'}).click();
// Confirm delete
const confirmation = page.getByTestId('confirmation-modal');
expect(confirmation).toContainText('Delete recommendation');
expect(confirmation).toContainText('Your recommendation Recommendation 1 title will no longer be visible to your audience.');
await confirmation.getByRole('button', {name: 'Delete'}).click();
await expect(page.getByTestId('toast-success')).toContainText('Successfully deleted the recommendation');
expect(lastApiRequests.deleteRecommendation).toBeTruthy();
});
test('can view incoming recommendations', async ({page}) => {
await mockApi({page, requests: {
...globalDataRequests,
browseIncomingRecommendations: {method: 'GET', path: '/incoming_recommendations/?limit=5&order=created_at+desc', response: responseFixtures.incomingRecommendations}
}});
await page.goto('/');
const section = page.getByTestId('recommendations');
const activeTab = section.locator('[role=tabpanel]:not(.hidden)');
await section.getByRole('tab', {name: 'Recommending you'}).click();
const recommendation1 = activeTab.getByTestId('incoming-recommendation-list-item').first();
const recommendation2 = activeTab.getByTestId('incoming-recommendation-list-item').last();
// Can recommend back
await expect(recommendation1).toContainText('Incoming recommendation 1 title');
await expect(recommendation1).toContainText('Recommend back');
// Already recommending back
await expect(recommendation2).toContainText('Incoming recommendation 2 title');
await expect(recommendation2).toContainText('Recommending');
});
});