3ef8b53fad
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>
204 lines
10 KiB
TypeScript
204 lines
10 KiB
TypeScript
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. You’ve 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. You’ve 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');
|
||
});
|
||
});
|