Skip to content
This repository was archived by the owner on Mar 12, 2025. It is now read-only.

Commit 82933ae

Browse files
Sembaukesidemt
andauthored
fix: feature image type and feature image saving (#377)
Co-authored-by: sidemt <[email protected]>
1 parent df27f82 commit 82933ae

File tree

7 files changed

+205
-8
lines changed

7 files changed

+205
-8
lines changed

apps/backend/src/api/post/content-types/post/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"feature_image": {
4040
"type": "media",
41-
"multiple": true,
41+
"multiple": false,
4242
"required": false,
4343
"allowedTypes": ["images"],
4444
"pluginOptions": {

apps/frontend/src/components/editor-drawer.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ const EditorDrawer = ({
181181
variant="ghost"
182182
onClick={onOpen}
183183
aria-label="Open Post Drawer"
184+
data-testid="open-post-drawer"
184185
icon={<FontAwesomeIcon icon={faGear} />}
185186
/>
186187
</Box>
@@ -245,7 +246,7 @@ const EditorDrawer = ({
245246
) : (
246247
<Img
247248
src={featureImage}
248-
alt="feature"
249+
data-testid="feature-image"
249250
borderRadius="lg"
250251
objectFit="cover"
251252
w="100%"
@@ -257,6 +258,7 @@ const EditorDrawer = ({
257258
{featureImage && (
258259
<Button
259260
colorScheme="red"
261+
data-testid="delete-feature-image"
260262
w="100%"
261263
onClick={() => handleFeatureImageChange(null, null)}
262264
>

apps/frontend/src/components/post-form.jsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ const PostForm = ({ tags, user, authors, post }) => {
5959

6060
if (feature_image.data) {
6161
setFeatureImageUrl(
62-
new URL(feature_image.data[0].attributes.url, apiBase),
62+
new URL(feature_image.data.attributes.formats.thumbnail.url, apiBase),
6363
);
64-
setFeatureImageId(feature_image.data[0].id);
64+
setFeatureImageId(feature_image.data.id);
6565
}
6666
}
6767
}, [post]);
@@ -104,7 +104,7 @@ const PostForm = ({ tags, user, authors, post }) => {
104104
const data = {
105105
data: {
106106
title: title,
107-
feature_image: featureImageId !== null ? [featureImageId] : [],
107+
feature_image: featureImageId ? [featureImageId] : null,
108108
slug: slugify(
109109
postUrl != "" ? postUrl : title != "(UNTITLED)" ? title : nonce,
110110
{
@@ -156,7 +156,6 @@ const PostForm = ({ tags, user, authors, post }) => {
156156
data.data.publishedAt = new Date().toISOString();
157157
data.data.scheduled_at = null;
158158
}
159-
160159
try {
161160
await updatePost(postId, data, token);
162161
toast({
@@ -168,7 +167,6 @@ const PostForm = ({ tags, user, authors, post }) => {
168167
duration: 5000,
169168
isClosable: true,
170169
});
171-
172170
setUnsavedChanges(false);
173171
} catch (error) {
174172
toast({

apps/frontend/src/lib/posts.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export async function createPost(data, token) {
118118
}
119119

120120
export async function updatePost(postId, data, token) {
121-
const url = new URL(`/api/posts/${postId}`, base);
121+
const url = new URL(`/api/posts/${postId}?populate=feature_image`, base);
122122

123123
const options = {
124124
method: "PUT",

e2e/editor-drawer.spec.ts

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { test, expect } from "@playwright/test";
2+
import path from 'path';
3+
4+
import { deletePost, getPostIdInURL, createPostWithFeatureImage } from "./helpers/post";
5+
6+
test.describe('feature image', () => {
7+
let postIdsToDelete: string[] = [];
8+
9+
test.afterAll(async ({ request }) => {
10+
// delete all posts created in the tests
11+
for (const postId of postIdsToDelete) {
12+
await deletePost(request, postId);
13+
}
14+
})
15+
16+
test('it should be possible to save without a feature image', async ({ page, request }) => {
17+
// Open a new post
18+
await page.goto('/posts');
19+
await page.getByRole('button', { name: 'New post' }).click();
20+
await page.waitForURL(/.*\/posts\/\d+/);
21+
// Store the post id to delete after the test
22+
const postId = getPostIdInURL(page);
23+
if (postId) {
24+
// Store the post id to delete after the test
25+
postIdsToDelete.push(postId);
26+
}
27+
28+
// Wait for the editor to load
29+
await page.getByTestId('editor').waitFor();
30+
31+
// Save the new post without adding a feature image
32+
await page.keyboard.down('Control');
33+
await page.keyboard.press('s');
34+
35+
// Check that the post was saved successfully
36+
const saveNotificationTitle = page.locator('#toast-1-title');
37+
const saveNotificationDescription = page.locator('#toast-1-description');
38+
expect(saveNotificationTitle).toBeVisible();
39+
expect(await saveNotificationTitle.innerText()).toBe('Post has been updated.');
40+
41+
expect(saveNotificationDescription).toBeVisible();
42+
expect(await saveNotificationDescription.innerText()).toBe('The post has been updated.');
43+
})
44+
45+
test('it should be possible to save with a feature image', async ({ page, request }) => {
46+
// Open a new post
47+
await page.goto('/posts');
48+
await page.getByRole('button', { name: 'New post' }).click();
49+
await page.waitForURL(/.*\/posts\/\d+/);
50+
// Store the post id to delete after the test
51+
const postId = getPostIdInURL(page); // Extract the new post id from the URL
52+
if (postId) {
53+
// Store the post id to delete after the test
54+
postIdsToDelete.push(postId);
55+
}
56+
57+
// Prepare promises before clicking the button
58+
const fileChooserPromise = page.waitForEvent('filechooser');
59+
const waitForUploadPromise = page.waitForResponse('**/api/upload');
60+
61+
// Open the drawer
62+
const drawerButton = page.getByTestId('open-post-drawer');
63+
await drawerButton.click();
64+
65+
// Select a feature image
66+
const fileChooserButton = page.locator('text="Select Image"');
67+
await fileChooserButton.click();
68+
const fileChooser = await fileChooserPromise;
69+
await fileChooser.setFiles(path.join(__dirname, '/fixtures/feature-image.png'));
70+
71+
// Wait for feature image upload to complete before saving the post
72+
await waitForUploadPromise;
73+
74+
// Prepare a promise before pressing the save shortcut
75+
const waitForSavePromise = page.waitForResponse(`**/api/posts/${postId}?populate=feature_image`);
76+
77+
// Save the new post with a feature image
78+
await page.keyboard.down('Control');
79+
await page.keyboard.press('s');
80+
81+
// Wait for the request to complete
82+
await waitForSavePromise;
83+
84+
// Check that the post was saved successfully
85+
const saveNotificationTitle = page.locator('#toast-1-title');
86+
expect(saveNotificationTitle).toBeVisible();
87+
expect(await saveNotificationTitle.innerText()).toBe('Post has been updated.');
88+
})
89+
90+
test('the saved image should be visible in the drawer and can be deleted', async ({ page, request }) => {
91+
// Prepare existing post that has a feature image
92+
const postId = await createPostWithFeatureImage(page, request);
93+
if (postId) {
94+
// Store the post id to delete after the test
95+
postIdsToDelete.push(postId);
96+
}
97+
98+
// Open the post
99+
await page.goto(`/posts/${postId}`);
100+
101+
// Check that saved feature image is visible in the drawer
102+
await page.getByTestId('open-post-drawer').click();
103+
expect(page.getByTestId('feature-image')).toBeVisible();
104+
105+
// Check that it's possible to delete the feature image
106+
const deleteImageButton = page.getByTestId('delete-feature-image');
107+
await deleteImageButton.click();
108+
109+
// Prepare a promise before pressing the save shortcut
110+
const nextPromise = page.waitForResponse(`**/api/posts/${postId}?populate=feature_image`);
111+
await page.keyboard.down('Control');
112+
await page.keyboard.press('s');
113+
114+
// Wait for the request to complete
115+
await nextPromise;
116+
117+
// Wait for the save notification to appear
118+
const saveNotificationTitle = page.locator('#toast-1-title');
119+
expect(saveNotificationTitle).toBeVisible();
120+
expect(await saveNotificationTitle.innerText()).toBe('Post has been updated.');
121+
122+
// Reopen the post
123+
await page.goto(`/posts/${postId}`);
124+
await page.getByTestId('open-post-drawer').click();
125+
// Wait for the drawer to open
126+
await page.locator('text="Select Image"').waitFor();
127+
128+
// Check that deleted image has dissapeared
129+
expect(page.getByTestId('feature-image')).not.toBeVisible();
130+
});
131+
})

e2e/fixtures/feature-image.png

5.18 KB
Loading

e2e/helpers/post.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Page, APIRequestContext } from "@playwright/test";
2+
const path = require("path");
3+
const fs = require('fs');
4+
const fetch = require('node-fetch');
5+
const FormData = require('form-data');
6+
7+
import { API_URL, EDITOR_CREDENTIALS } from "./constants";
8+
import { getBearerToken } from "./user";
9+
10+
export async function deletePost(
11+
request: APIRequestContext,
12+
postId: string
13+
) {
14+
const jwt = await getBearerToken(request, EDITOR_CREDENTIALS);
15+
await request.delete(`${API_URL}/api/posts/${postId}`, {
16+
headers: {
17+
Authorization: `Bearer ${jwt}`,
18+
},
19+
});
20+
}
21+
22+
export function getPostIdInURL(page: Page) {
23+
const pageUrl = page.url();
24+
const match = pageUrl.match(/.*\/posts\/(\d+)/);
25+
const postId = match ? match[1] : null; // Extract the new post id from the URL
26+
return postId;
27+
}
28+
29+
export async function createPostWithFeatureImage(page: Page, request: APIRequestContext) {
30+
// Create a new post via API
31+
const jwt = await getBearerToken(request, EDITOR_CREDENTIALS);
32+
const timestamp = Date.now();
33+
const createPostRes = await request.post(`${API_URL}/api/posts`, {
34+
headers: {
35+
Authorization: `Bearer ${jwt}`,
36+
},
37+
data: {
38+
data: {
39+
title: `Test Post ${timestamp}`,
40+
slug: `test-post-${timestamp}`, // Make sure the slug is unique
41+
body: 'Test post body',
42+
author: [1]
43+
}
44+
},
45+
});
46+
const postId = (await createPostRes.json()).data.id;
47+
48+
// Attach a feature image to the post
49+
const formData = new FormData();
50+
const image = fs.createReadStream(path.join(__dirname, '..', 'fixtures', 'feature-image.png'));
51+
formData.append('files', image);
52+
formData.append('refId', postId);
53+
formData.append('ref', 'api::post.post');
54+
formData.append('field', 'feature_image');
55+
// Using fetch here since Playwright's request context didn't work
56+
const uploadRes = await fetch(new URL('api/upload', API_URL), {
57+
method: 'POST',
58+
headers: {
59+
Accept: "application/json",
60+
Authorization: `Bearer ${jwt}`,
61+
},
62+
body: formData,
63+
});
64+
65+
return postId;
66+
}

0 commit comments

Comments
 (0)