// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT // @watch start // templates/repo/editor/** // web_src/js/features/common-global.js // routers/web/web.go // services/repository/files/** // @watch end import {expect} from '@playwright/test'; import {test, dynamic_id} from './utils_e2e.ts'; test.use({user: 'user2'}); interface TestCase { description: string; files: string[]; } async function doUpload({page}, testCase: TestCase) { await page.goto(`/user2/file-uploads/_upload/main/`); const testID = dynamic_id(); const dropzone = page.getByRole('button', {name: 'Drop files or click here to upload.'}); // create the virtual files const dataTransfer = await page.evaluateHandle((testCase: TestCase) => { const dt = new DataTransfer(); for (const filename of testCase.files) { dt.items.add(new File([`File content of ${filename}`], filename, {type: 'text/plain'})); } return dt; }, testCase); // and drop them to the upload area await dropzone.dispatchEvent('drop', {dataTransfer}); await page.getByText('new branch').click(); await page.getByRole('textbox', {name: 'Name the new branch for this'}).fill(testID); // ToDo: Potential race condition: We do not currently wait for the upload to complete. // See https://codeberg.org/forgejo/forgejo/pulls/6687#issuecomment-5068272 and // https://codeberg.org/forgejo/forgejo/issues/5893#issuecomment-5068266 for details. // Workaround is to wait (the uploads are just a few bytes and usually complete instantly) // // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(100); await page.getByRole('button', {name: 'Propose file change'}).click(); } test.describe('Drag and Drop upload', () => { const goodTestCases: TestCase[] = [ { description: 'normal and special characters', files: [ 'dir1/file1.txt', 'double/nested/file.txt', 'special/äüöÄÜÖß.txt', 'special/Ʉ₦ł₵ØĐɆ.txt', ], }, { description: 'strange paths and spaces', files: [ '..dots.txt', '.dots.preserved.txt', 'special/S P A C E !.txt', ], }, ]; // actual good tests based on definition above for (const testCase of goodTestCases) { test(`good: ${testCase.description}`, async ({page}) => { await doUpload({page}, testCase); // check that nested file structure is preserved for (const filename of testCase.files) { await expect(page.locator('#diff-file-boxes').getByRole('link', {name: filename})).toBeVisible(); } }); } const badTestCases: TestCase[] = [ { description: 'broken path slash in front', files: [ '/special/badfirstslash.txt', ], }, ]; // actual bad tests based on definition above for (const testCase of badTestCases) { test(`bad: ${testCase.description}`, async ({page}) => { await doUpload({page}, testCase); await expect(page.getByText('Failed to upload files to')).toBeVisible(); }); } });