刘光辉
12 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// @vitest-environment node
 
import type { Server as HttpServer } from 'node:http';
 
import { unlinkSync, writeFileSync } from 'node:fs';
import { createServer as createHttpServer } from 'node:http';
import { resolve } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
 
import { createServer, loadConfigFromFile } from 'vite';
import { afterEach, describe, expect, it } from 'vitest';
 
const pluginRoot = fileURLToPath(new URL('../..', import.meta.url));
const repositoryRoot = resolve(pluginRoot, '../../..');
const configFile = resolve(pluginRoot, 'vite.config.ts');
const environmentNames = [
  'ONLYOFFICE_DOCS_URL',
  'ONLYOFFICE_PLUGIN_DEV_HOST',
  'ONLYOFFICE_PLUGIN_DEV_PORT',
  'ONLYOFFICE_PLUGIN_HMR_CLIENT_PORT',
  'ONLYOFFICE_PLUGIN_HMR_HOST',
  'ONLYOFFICE_PLUGIN_HMR_PROTOCOL',
  'ONLYOFFICE_PLUGIN_PUBLIC_HOST',
] as const;
const originalEnvironment = Object.fromEntries(environmentNames.map((name) => [name, process.env[name]]));
const testEnvironmentPath = resolve(pluginRoot, '.env.task4-integration.local');
 
async function reservePort(): Promise<number> {
  const server = createHttpServer();
  await new Promise<void>((resolveListen, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => resolveListen());
  });
  const address = server.address();
  if (!address || typeof address === 'string') throw new Error('未获得测试端口');
  const port = address.port;
  await close(server);
  return port;
}
 
async function close(server: HttpServer): Promise<void> {
  await new Promise<void>((resolveClose, reject) => server.close((error) => (error ? reject(error) : resolveClose())));
}
 
describe('真实 Vite config 集成', () => {
  afterEach(() => {
    process.chdir(pluginRoot);
    try {
      unlinkSync(testEnvironmentPath);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
    }
    for (const name of environmentNames) {
      const value = originalEnvironment[name];
      if (value === undefined) delete process.env[name];
      else process.env[name] = value;
    }
  });
 
  it('从仓库根解析 build config 时仍使用插件根和正式构建语义', async () => {
    process.chdir(repositoryRoot);
 
    const loaded = await loadConfigFromFile(
      { command: 'build', isPreview: false, isSsrBuild: false, mode: 'production' },
      configFile,
      repositoryRoot,
      'silent',
    );
 
    expect(loaded?.config?.base).toBe('./');
    expect(resolve(loaded?.config?.root ?? '')).toBe(resolve(pluginRoot));
    expect(loaded?.config.build?.rollupOptions?.input).toBe(resolve(pluginRoot, 'index.html'));
    expect(loaded?.config.plugins).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'eln-production-assets' })]));
  });
 
  it('从仓库根用真实 config 启动 serve 并应用 env、base、server 与开发插件', async () => {
    const port = await reservePort();
    writeFileSync(
      testEnvironmentPath,
      [
        'ONLYOFFICE_PLUGIN_DEV_HOST=127.0.0.1',
        `ONLYOFFICE_PLUGIN_DEV_PORT=${port}`,
        'ONLYOFFICE_PLUGIN_PUBLIC_HOST=localhost',
        'ONLYOFFICE_PLUGIN_HMR_HOST=localhost',
        `ONLYOFFICE_PLUGIN_HMR_CLIENT_PORT=${port}`,
      ].join('\n'),
    );
    for (const name of environmentNames) delete process.env[name];
    process.chdir(repositoryRoot);
    const server = await createServer({ configFile, logLevel: 'silent', mode: 'task4-integration' });
 
    try {
      expect(resolve(server.config.root)).toBe(resolve(pluginRoot));
      expect(server.config.base).toBe('/onlyoffice-plugins/0.1.0-dev/eln-template-annotator/');
      expect(server.config.server).toMatchObject({
        hmr: { clientPort: port, host: 'localhost', protocol: 'ws' },
        host: '127.0.0.1',
        port,
        strictPort: true,
      });
      expect(server.config.plugins).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'onlyoffice-development' })]));
 
      await server.listen();
      const response = await fetch(`http://127.0.0.1:${port}${server.config.base}config.json`);
      expect(response.status).toBe(200);
      await expect(response.json()).resolves.toMatchObject({ version: '0.1.0' });
 
      const indexResponse = await fetch(`http://127.0.0.1:${port}${server.config.base}index.html?v=integration`);
      expect(indexResponse.status).toBe(200);
      const indexHtml = await indexResponse.text();
      expect(indexHtml).toContain('src="/onlyoffice-sdk/v1/plugins.js"');
      expect(indexHtml).toContain('href="/onlyoffice-sdk/v1/plugins.css"');
 
      for (const resource of ['@vite/client', 'resources/icon.png', 'src/main.ts']) {
        const resourceResponse = await fetch(`http://127.0.0.1:${port}${server.config.base}${resource}`);
        expect(resourceResponse.status).toBe(200);
        await resourceResponse.arrayBuffer();
      }
      await server.waitForRequestsIdle();
    } finally {
      await server.close();
    }
  });
});