Migrate from Jest to Vitest

This commit is contained in:
ChaosExAnima 2025-04-14 16:34:32 +02:00
parent 23238ddd95
commit bc8f481eb8
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
21 changed files with 1195 additions and 96 deletions

2
.gitignore vendored
View File

@ -74,3 +74,5 @@ docker-compose.override.yml
# Ignore local-only rspec configuration
.rspec-local
/.dist

View File

@ -1,4 +1,3 @@
import './public-path';
import { createRoot } from 'react-dom/client';
import Rails from '@rails/ujs';

View File

@ -1,9 +1,7 @@
import './public-path';
import { start } from 'mastodon/common';
import { loadLocale } from 'mastodon/locales';
import main from 'mastodon/main';
import { start } from '../mastodon/common';
import { loadLocale } from '../mastodon/locales';
import { loadPolyfills } from '../mastodon/polyfills';
import { loadPolyfills } from 'mastodon/polyfills';
start();

View File

@ -1,4 +1,3 @@
import './public-path';
import { createRoot } from 'react-dom/client';
import { afterInitialRender } from 'mastodon/hooks/useRenderSignal';

View File

@ -1,4 +1,3 @@
import './public-path';
import ready from '../mastodon/ready';
ready(() => {

View File

@ -0,0 +1,8 @@
<html>
<head>
<script type="module" src="./application.ts"></script>
</head>
<body>
<div id="mastodon"></div>
</body>
</html>

View File

@ -1,3 +1 @@
import '../styles/mailer.scss';
require.context('../icons');

View File

@ -1,23 +0,0 @@
// Dynamically set webpack's loading path depending on a meta header, in order
// to share the same assets regardless of instance configuration.
// See https://webpack.js.org/guides/public-path/#on-the-fly
function removeOuterSlashes(string: string) {
return string.replace(/^\/*/, '').replace(/\/*$/, '');
}
function formatPublicPath(host = '', path = '') {
let formattedHost = removeOuterSlashes(host);
if (formattedHost && !/^http/i.test(formattedHost)) {
formattedHost = `//${formattedHost}`;
}
const formattedPath = removeOuterSlashes(path);
return `${formattedHost}/${formattedPath}/`;
}
const cdnHost = document.querySelector<HTMLMetaElement>('meta[name=cdn-host]');
__webpack_public_path__ = formatPublicPath(
cdnHost ? cdnHost.content : '',
process.env.PUBLIC_OUTPUT_PATH,
);

View File

@ -1,7 +1,5 @@
import { createRoot } from 'react-dom/client';
import './public-path';
import { IntlMessageFormat } from 'intl-messageformat';
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
import { defineMessages } from 'react-intl';

View File

@ -8,8 +8,6 @@ and performs no other task.
*/
import './public-path';
import axios from 'axios';
interface JRDLink {

View File

@ -1,4 +1,3 @@
import './public-path';
import { createRoot } from 'react-dom/client';
import { start } from '../mastodon/common';

View File

@ -1,4 +1,3 @@
import './public-path';
import axios from 'axios';
import ready from '../mastodon/ready';

View File

@ -9,14 +9,13 @@
// to ensure that the prevaled file is regenerated by Babel
// version: 4
const { NimbleEmojiIndex } = require('emoji-mart');
const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
import { NimbleEmojiIndex } from 'emoji-mart';
import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data';
let data = require('./emoji_data.json');
const emojiMap = require('./emoji_map.json');
const { unicodeToFilename } = require('./unicode_to_filename');
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
import data from './emoji_data.json';
import emojiMap from './emoji_map.json';
import { unicodeToFilename } from './unicode_to_filename';
import { unicodeToUnifiedName } from './unicode_to_unified_name';
emojiMartUncompress(data);
@ -117,7 +116,7 @@ Object.keys(emojiIndex.emojis).forEach(key => {
// JSON.parse/stringify is to emulate what @preval is doing and avoid any
// inconsistent behavior in dev mode
module.exports = JSON.parse(JSON.stringify([
export default JSON.parse(JSON.stringify([
shortCodesToEmojiData,
/*
* The property `skins` is not found in the current context.

View File

@ -1,6 +1,6 @@
// taken from:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => {
export const unicodeToFilename = (str) => {
let result = '';
let charCode = 0;
let p = 0;

View File

@ -6,7 +6,7 @@ function padLeft(str, num) {
return str;
}
exports.unicodeToUnifiedName = (str) => {
export const unicodeToUnifiedName = (str) => {
let output = '';
for (let i = 0; i < str.length; i += 2) {

View File

@ -5,6 +5,10 @@ import { isLocaleLoaded, setLocale } from './global_locale';
const localeLoadingSemaphore = new Semaphore(1);
const localeFiles = import.meta.glob<{ default: LocaleData['messages'] }>([
'./*.json',
]);
export async function loadLocale() {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to match empty strings
const locale = document.querySelector<HTMLElement>('html')?.lang || 'en';
@ -17,13 +21,14 @@ export async function loadLocale() {
// if the locale is already set, then do nothing
if (isLocaleLoaded()) return;
const localeData = (await import(
/* webpackMode: "lazy" */
/* webpackChunkName: "locale/[request]" */
/* webpackInclude: /\.json$/ */
/* webpackPreload: true */
`mastodon/locales/${locale}.json`
)) as LocaleData['messages'];
// If there is no locale file, then fallback to english
const localeFile = Object.hasOwn(localeFiles, '`./${locale}.json`')
? localeFiles[`./${locale}.json`]
: localeFiles[`./en.json`];
if (!localeFile) throw new Error('Could not load the locale JSON file');
const { default: localeData } = await localeFile();
setLocale({ messages: localeData, locale });
});

View File

@ -54,11 +54,9 @@ async function loadIntlPluralRulesPolyfills(locale: string) {
return;
}
// Load the polyfill 1st BEFORE loading data
await import('@formatjs/intl-pluralrules/polyfill-force');
await import(
/* webpackChunkName: "i18n-pluralrules-polyfill" */ '@formatjs/intl-pluralrules/polyfill-force'
);
await import(
/* webpackChunkName: "i18n-pluralrules-polyfill-[request]" */ `@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}`
`../../../../node_modules/@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js`
);
}

View File

@ -0,0 +1,90 @@
import { createHash } from 'node:crypto';
import { promises as fs } from 'node:fs';
import { resolve } from 'node:path';
import type { Plugin, Manifest } from 'vite';
export type Algorithm = 'sha256' | 'sha384' | 'sha512';
export interface Options {
/**
* Which hashing algorithms to use when calculate the integrity hash for each
* asset in the manifest.
* @default ['sha384']
*/
algorithms?: Algorithm[];
/**
* Path of the manifest files that should be read and augmented with the
* integrity hash, relative to `outDir`.
* @default ['manifest.json', 'manifest-assets.json']
*/
manifestPaths?: string[];
}
declare module 'vite' {
interface ManifestChunk {
integrity: string;
}
}
export function manifestSRI(options: Options = {}): Plugin {
const {
algorithms = ['sha384'],
manifestPaths = [
'.vite/manifest.json',
'.vite/manifest-assets.json',
'manifest.json',
'manifest-assets.json',
],
} = options;
return {
name: 'vite-plugin-manifest-sri',
apply: 'build',
enforce: 'post',
async writeBundle({ dir }) {
await Promise.all(
manifestPaths.map((path) =>
augmentManifest(path, algorithms, dir ?? ''),
),
);
},
};
}
async function augmentManifest(
manifestPath: string,
algorithms: string[],
outDir: string,
) {
const resolveInOutDir = (path: string) => resolve(outDir, path);
manifestPath = resolveInOutDir(manifestPath);
const manifest: Manifest | undefined = await fs
.readFile(manifestPath, 'utf-8')
.then((file) => JSON.parse(file) as Manifest);
if (manifest) {
await Promise.all(
Object.values(manifest).map(async (chunk) => {
chunk.integrity = integrityForAsset(
await fs.readFile(resolveInOutDir(chunk.file)),
algorithms,
);
}),
);
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
}
}
function integrityForAsset(source: Buffer, algorithms: string[]) {
return algorithms
.map((algorithm) => calculateIntegrityHash(source, algorithm))
.join(' ');
}
export function calculateIntegrityHash(source: Buffer, algorithm: string) {
const hash = createHash(algorithm).update(source).digest().toString('base64');
return `${algorithm.toLowerCase()}-${hash}`;
}

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"jsx": "react-jsx",
"target": "esnext",
"module": "CommonJS",
"module": "ES2022",
"moduleResolution": "node",
"allowJs": true,
"noEmit": true,
@ -11,7 +11,7 @@
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["vitest/globals", "@types/webpack-env"],
"types": ["vite/client", "vitest/globals", "@types/webpack-env"],
"baseUrl": "./",
"incremental": true,
"tsBuildInfoFile": "tmp/cache/tsconfig.tsbuildinfo",

54
vite.config.ts Normal file
View File

@ -0,0 +1,54 @@
import path from 'node:path';
import { defineConfig } from 'vite';
import { manifestSRI } from './config/vite/plugin-manifest-sri';
// eslint-disable-next-line import/no-default-export
export default defineConfig({
root: './app/javascript/entrypoints',
build: {
commonjsOptions: { transformMixedEsModules: true },
outDir: path.resolve(__dirname, '.dist'),
emptyOutDir: true,
manifest: 'manifest.json',
rollupOptions: {
output: {
chunkFileNames: (chunkInfo) => {
if (
chunkInfo.facadeModuleId?.match(
/mastodon\/locales\/[a-zA-Z-]+\.json/,
)
) {
// put all locale files in `intl/`
return `intl/[name]-[hash].js`;
} else if (
chunkInfo.facadeModuleId?.match(/node_modules\/@formatjs\//)
) {
// use a custom name for formatjs polyfill files
const name = /node_modules\/@formatjs\/([^/]+)\//.exec(
chunkInfo.facadeModuleId,
);
if (name?.[1]) return `intl/[name]-${name[1]}-[hash].js`;
} else if (chunkInfo.name === 'index' && chunkInfo.facadeModuleId) {
// Use a custom name for chunks, to avoid having too many of them called "index"
const parts = chunkInfo.facadeModuleId.split('/');
const parent = parts.at(-2);
if (parent) return `${parent}-[name]-[hash].js`;
}
return `[name]-[hash].js`;
},
},
},
},
plugins: [manifestSRI()],
resolve: {
alias: {
mastodon: path.resolve(__dirname, 'app/javascript/mastodon'),
'@': path.resolve(__dirname, 'app/javascript'),
},
},
});

1047
yarn.lock

File diff suppressed because it is too large Load Diff