e2e: add tests for translations (#114390)

e2e: add tests for translations
This commit is contained in:
Hugo Häggmark
2025-12-08 10:19:44 +01:00
committed by GitHub
parent 8bf3ac9710
commit 3490c3b0fd
46 changed files with 678 additions and 20 deletions
@@ -0,0 +1 @@
# Changelog
@@ -0,0 +1,83 @@
import { css, cx } from '@emotion/css';
import { useStyles2, useTheme2 } from '@grafana/ui';
import { PanelDataErrorView } from '@grafana/runtime';
import { PanelProps } from '@grafana/data';
import React from 'react';
import { SimpleOptions } from '../types';
import { Trans } from '@grafana/i18n';
interface Props extends PanelProps<SimpleOptions> {}
const getStyles = () => {
return {
wrapper: css`
font-family: Open Sans;
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
};
export const SimplePanel: React.FC<Props> = ({ options, data, width, height, fieldConfig, id }) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
if (data.series.length === 0) {
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
}
return (
<div
className={cx(
styles.wrapper,
css`
width: ${width}px;
height: ${height}px;
`
)}
>
<svg
className={styles.svg}
width={width}
height={height}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox={`-${width / 2} -${height / 2} ${width} ${height}`}
>
<g>
<circle data-testid="simple-panel-circle" style={{ fill: theme.colors.primary.main }} r={100} />
</g>
</svg>
<div className={styles.textBox}>
{options.showSeriesCount && (
<div data-testid="simple-panel-series-counter">
<Trans
i18nKey="components.simplePanel.options.showSeriesCount"
defaults="Number of series: {{numberOfSeries}}"
values={{ numberOfSeries: data.series.length }}
/>
</div>
)}
<div>
<Trans
i18nKey="components.simplePanel.options.textOptionValue"
defaults="Text option value: {{optionValue}}"
values={{ optionValue: options.text }}
/>
</div>
</div>
</div>
);
};
@@ -0,0 +1,13 @@
import { defineConfig } from 'i18next-cli';
import pluginJson from './plugin.json';
export default defineConfig({
locales: pluginJson.languages,
extract: {
input: ['**/*.{tsx,ts}'],
output: 'locales/{{language}}/{{namespace}}.json',
defaultNS: pluginJson.id,
functions: ['t', '*.t'],
transComponents: ['Trans'],
},
});
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81.9 71.52"><defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:#3865ab;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="42.95" y1="16.88" x2="81.9" y2="16.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M55.46,62.43A2,2,0,0,1,54.07,59l4.72-4.54a2,2,0,0,1,2.2-.39l3.65,1.63,3.68-3.64a2,2,0,1,1,2.81,2.84l-4.64,4.6a2,2,0,0,1-2.22.41L60.6,58.26l-3.76,3.61A2,2,0,0,1,55.46,62.43Z"/><path class="cls-2" d="M37,0H2A2,2,0,0,0,0,2V31.76a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V2A2,2,0,0,0,37,0ZM4,29.76V8.84H35V29.76Z"/><path class="cls-3" d="M79.9,0H45a2,2,0,0,0-2,2V31.76a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V2A2,2,0,0,0,79.9,0ZM47,29.76V8.84h31V29.76Z"/><path class="cls-2" d="M37,37.76H2a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V39.76A2,2,0,0,0,37,37.76ZM4,67.52V46.6H35V67.52Z"/><path class="cls-2" d="M79.9,37.76H45a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V39.76A2,2,0,0,0,79.9,37.76ZM47,67.52V46.6h31V67.52Z"/><rect class="cls-1" x="10.48" y="56.95" width="4" height="5.79"/><rect class="cls-1" x="17.43" y="53.95" width="4" height="8.79"/><rect class="cls-1" x="24.47" y="50.95" width="4" height="11.79"/><path class="cls-1" d="M19.47,25.8a6.93,6.93,0,1,1,6.93-6.92A6.93,6.93,0,0,1,19.47,25.8Zm0-9.85a2.93,2.93,0,1,0,2.93,2.93A2.93,2.93,0,0,0,19.47,16Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,30 @@
{
"components": {
"simplePanel": {
"options": {
"showSeriesCount": "Number of series: {{numberOfSeries}}",
"textOptionValue": "Text option value: {{optionValue}}"
}
}
},
"panel": {
"options": {
"seriesCountSize": {
"name": "Series counter size",
"options": {
"lg": "Large",
"md": "Medium",
"sm": "Small"
}
},
"showSeriesCount": {
"name": "Show series counter"
},
"text": {
"defaultValue": "Default value of text input option",
"description": "Description of panel option",
"name": "Simple text option"
}
}
}
}
@@ -0,0 +1,30 @@
{
"components": {
"simplePanel": {
"options": {
"showSeriesCount": "Número de series: {{numberOfSeries}}",
"textOptionValue": "Valor de opción de texto: {{optionValue}}"
}
}
},
"panel": {
"options": {
"seriesCountSize": {
"name": "Tamaño del contador de series",
"options": {
"lg": "Grande",
"md": "Mediano",
"sm": "Pequeño"
}
},
"showSeriesCount": {
"name": "Mostrar contador de series"
},
"text": {
"defaultValue": "Valor predeterminado de la opción de entrada de texto",
"description": "Descripción de la opción del panel",
"name": "Opción de texto simple"
}
}
}
}
@@ -0,0 +1,30 @@
{
"components": {
"simplePanel": {
"options": {
"showSeriesCount": "Antal serier: {{numberOfSeries}}",
"textOptionValue": "Textalternativ värde: {{optionValue}}"
}
}
},
"panel": {
"options": {
"seriesCountSize": {
"name": "Storlek på serieräknare",
"options": {
"lg": "Stor",
"md": "Medel",
"sm": "Liten"
}
},
"showSeriesCount": {
"name": "Visa serieräknare"
},
"text": {
"defaultValue": "Standardvärde för textinmatningsalternativ",
"description": "Beskrivning av panelalternativ",
"name": "Enkelt textalternativ"
}
}
}
}
@@ -0,0 +1,46 @@
import { initPluginTranslations, t } from '@grafana/i18n';
import { PanelPlugin } from '@grafana/data';
import { SimpleOptions } from './types';
import { SimplePanel } from './components/SimplePanel';
import pluginJson from './plugin.json';
await initPluginTranslations(pluginJson.id);
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
return builder
.addTextInput({
path: 'text',
name: t('panel.options.text.name', 'Simple text option'),
description: t('panel.options.text.description', 'Description of panel option'),
defaultValue: t('panel.options.text.defaultValue', 'Default value of text input option'),
})
.addBooleanSwitch({
path: 'showSeriesCount',
name: t('panel.options.showSeriesCount.name', 'Show series counter'),
defaultValue: false,
})
.addRadio({
path: 'seriesCountSize',
defaultValue: 'sm',
name: t('panel.options.seriesCountSize.name', 'Series counter size'),
settings: {
options: [
{
value: 'sm',
label: t('panel.options.seriesCountSize.options.sm', 'Small'),
},
{
value: 'md',
label: t('panel.options.seriesCountSize.options.md', 'Medium'),
},
{
value: 'lg',
label: t('panel.options.seriesCountSize.options.lg', 'Large'),
},
],
},
showIf: (config) => config.showSeriesCount,
});
});
@@ -0,0 +1,50 @@
{
"name": "@test-plugins/grafana-e2etest-panel",
"version": "12.4.0-pre",
"private": true,
"scripts": {
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
"dev": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -w -c ./webpack.config.ts --env development",
"typecheck": "tsc --noEmit",
"lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx .",
"i18n-extract": "i18next-cli extract --sync-primary"
},
"author": "Grafana",
"license": "Apache-2.0",
"devDependencies": {
"@grafana/plugin-configs": "workspace:*",
"@types/lodash": "4.17.7",
"@types/node": "24.9.2",
"@types/prismjs": "1.26.4",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/semver": "7.5.8",
"@types/uuid": "9.0.8",
"glob": "10.4.1",
"i18next-cli": "^1.24.22",
"ts-node": "10.9.2",
"typescript": "5.5.4",
"webpack": "5.95.0",
"webpack-merge": "5.10.0"
},
"engines": {
"node": ">= 22 <25"
},
"dependencies": {
"@emotion/css": "11.11.2",
"@grafana/data": "workspace:*",
"@grafana/i18n": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "^6.22.0",
"rxjs": "7.8.1",
"tslib": "2.6.3"
},
"peerDependencies": {
"@grafana/runtime": "*"
},
"packageManager": "yarn@4.11.0"
}
@@ -0,0 +1,26 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "panel",
"name": "Grafana E2ETest Panel",
"id": "grafana-e2etest-panel",
"info": {
"keywords": ["panel"],
"description": "",
"author": {
"name": "Grafana"
},
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"links": [],
"screenshots": [],
"version": "%VERSION%",
"updated": "%TODAY%"
},
"dependencies": {
"grafanaDependency": ">=12.0.0",
"plugins": []
},
"languages": ["en-US", "es-ES", "sv-SE"]
}
@@ -0,0 +1,13 @@
import { FRENCH_FRANCE } from '@grafana/i18n';
import { expect, test } from '@grafana/plugin-e2e';
test.use({ userPreferences: { language: FRENCH_FRANCE } });
test('should display default translation (en-US)', async ({ panelEditPage }) => {
panelEditPage.setVisualization('Grafana E2ETest Panel');
await expect(panelEditPage.panel.locator.getByText('Text option value:')).toBeVisible();
const options = panelEditPage.getCustomOptions('Grafana E2ETest Panel');
const showSeriesCounter = options.getSwitch('Show series counter');
await expect(showSeriesCounter.locator()).toBeVisible();
});
@@ -0,0 +1,13 @@
import { SWEDISH_SWEDEN } from '@grafana/i18n';
import { expect, test } from '@grafana/plugin-e2e';
test.use({ userPreferences: { language: SWEDISH_SWEDEN } });
test('should display correct translation', async ({ panelEditPage }) => {
panelEditPage.setVisualization('Grafana E2ETest Panel');
await expect(panelEditPage.panel.locator.getByText('Textalternativ värde:')).toBeVisible();
const options = panelEditPage.getCustomOptions('Grafana E2ETest Panel');
const showSeriesCounter = options.getSwitch('Visa serieräknare');
await expect(showSeriesCounter.locator()).toBeVisible();
});
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"types": ["node", "jest", "@testing-library/jest-dom"]
},
"extends": "@grafana/plugin-configs/tsconfig.json",
"include": ["."]
}
@@ -0,0 +1,7 @@
type SeriesSize = 'sm' | 'md' | 'lg';
export interface SimpleOptions {
text: string;
showSeriesCount: boolean;
seriesCountSize: SeriesSize;
}
@@ -0,0 +1,45 @@
import CopyWebpackPlugin from 'copy-webpack-plugin';
import grafanaConfig, { type Env } from '@grafana/plugin-configs/webpack.config.ts';
import { mergeWithCustomize, unique } from 'webpack-merge';
import { type Configuration } from 'webpack';
function skipFiles(f: string): boolean {
if (f.includes('/dist/')) {
// avoid copying files already in dist
return false;
}
if (f.includes('/node_modules/')) {
// avoid copying tsconfig.json
return false;
}
if (f.includes('/package.json')) {
// avoid copying package.json
return false;
}
return true;
}
const config = async (env: Env): Promise<Configuration> => {
const baseConfig = await grafanaConfig(env);
const customConfig = {
plugins: [
new CopyWebpackPlugin({
patterns: [
// To `compiler.options.output`
{ from: 'README.md', to: '.', force: true },
{ from: 'plugin.json', to: '.' },
{ from: 'CHANGELOG.md', to: '.', force: true },
{ from: '**/*.json', to: '.', filter: skipFiles },
{ from: '**/*.svg', to: '.', noErrorOnMissing: true, filter: skipFiles }, // Optional
],
}),
],
externals: [...(baseConfig.externals as any), 'i18next'],
};
return mergeWithCustomize({
customizeArray: unique('plugins', ['CopyPlugin'], (plugin) => plugin.constructor && plugin.constructor.name),
})(baseConfig, customConfig);
};
export default config;