> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# الاختبار

> إعداد Vitest، واختبارات تكامل مقابل خادم Twenty حقيقي، والتحقق من الأنواع، والتكامل المستمر (CI) باستخدام GitHub Actions.

يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي.

## استخدام حِزَم npm

يمكنك تثبيت واستخدام أي حزمة npm في تطبيقك. يتم تجميع كلٍ من الدوال المنطقية والمكوّنات الأمامية باستخدام [esbuild](https://esbuild.github.io/)، والذي يُضمّن جميع التبعيات ضمن المخرجات — لا حاجة إلى `node_modules` وقت التشغيل.

### تثبيت حزمة

```bash filename="Terminal" theme={null}
yarn add axios
```

ثم استوردها في شيفرتك:

```ts src/logic-functions/fetch-data.ts theme={null}
import { defineLogicFunction } from 'twenty-sdk/define';
import axios from 'axios';

const handler = async (): Promise<any> => {
  const { data } = await axios.get('https://api.example.com/data');

  return { data };
};

export default defineLogicFunction({
  universalIdentifier: '...',
  name: 'fetch-data',
  description: 'Fetches data from an external API',
  timeoutSeconds: 10,
  handler,
});
```

وينطبق الأمر نفسه على المكوّنات الأمامية:

```tsx src/front-components/chart.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { format } from 'date-fns';

const DateWidget = () => {
  return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'date-widget',
  component: DateWidget,
});
```

### كيف يعمل التجميع

تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة.

**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت.

**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح.

كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم.

## إعداد

يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات:

```bash filename="Terminal" theme={null}
yarn add -D vitest vite-tsconfig-paths
```

أنشئ `vitest.config.ts` في جذر تطبيقك:

```ts vitest.config.ts theme={null}
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';

// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;

export default defineConfig({
  plugins: [
    tsconfigPaths({
      projects: ['tsconfig.spec.json'],
      ignoreConfigErrors: true,
    }),
  ],
  test: {
    testTimeout: 120_000,
    hookTimeout: 120_000,
    fileParallelism: false,
    include: ['src/**/*.integration-test.ts'],
    globalSetup: ['src/__tests__/global-setup.ts'],
    env: {
      TWENTY_API_URL,
      TWENTY_API_KEY,
    },
  },
});
```

أنشئ ملف إعداد عام يتحقق من إمكانية الوصول إلى الخادم، ويكتب ملف إعداد اختبار لـ SDK (`~/.twenty/config.test.json`)، ويزامن التطبيق قبل تشغيل الاختبارات:

```ts src/__tests__/global-setup.ts theme={null}
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

import { appDevOnce, appUninstall } from 'twenty-sdk/cli';

const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');

export async function setup() {
  const apiUrl = process.env.TWENTY_API_URL!;
  const apiKey = process.env.TWENTY_API_KEY!;

  // Verify the server is running
  const response = await fetch(`${apiUrl}/healthz`);
  if (!response.ok) {
    throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
  }

  // Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
  fs.writeFileSync(
    path.join(CONFIG_DIR, 'config.test.json'),
    JSON.stringify({
      remotes: { local: { apiUrl, apiKey } },
      defaultRemote: 'local',
    }, null, 2),
  );

  // Start from a clean slate, then sync the app
  await appUninstall({ appPath: APP_PATH }).catch(() => {});

  const result = await appDevOnce({ appPath: APP_PATH });
  if (!result.success) {
    throw new Error(`Dev sync failed: ${result.error?.message}`);
  }
}

export async function teardown() {
  await appUninstall({ appPath: APP_PATH });
}
```

## واجهات SDK البرمجية

يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار:

| دالة           | الوصف                                                               |
| -------------- | ------------------------------------------------------------------- |
| `appBuild`     | بناء التطبيق واختياريًا حزم ملف tarball                             |
| `appDeploy`    | رفع ملف tarball إلى الخادم                                          |
| `appDevOnce`   | بناء التطبيق ومزامنته مرة واحدة (نفس الأمر مثل `yarn twenty apply`) |
| `appInstall`   | تثبيت التطبيق على مساحة العمل النشطة                                |
| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة                           |

تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`.

## كتابة اختبار تكامل

إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل:

```ts src/__tests__/app-install.integration-test.ts theme={null}
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const APP_PATH = process.cwd();

describe('App installation', () => {
  beforeAll(async () => {
    const buildResult = await appBuild({
      appPath: APP_PATH,
      tarball: true,
      onProgress: (message: string) => console.log(`[build] ${message}`),
    });

    if (!buildResult.success) {
      throw new Error(`Build failed: ${buildResult.error?.message}`);
    }

    const deployResult = await appDeploy({
      tarballPath: buildResult.data.tarballPath!,
      onProgress: (message: string) => console.log(`[deploy] ${message}`),
    });

    if (!deployResult.success) {
      throw new Error(`Deploy failed: ${deployResult.error?.message}`);
    }

    const installResult = await appInstall({ appPath: APP_PATH });

    if (!installResult.success) {
      throw new Error(`Install failed: ${installResult.error?.message}`);
    }
  });

  afterAll(async () => {
    await appUninstall({ appPath: APP_PATH });
  });

  it('should find the installed app in the workspace', async () => {
    const metadataClient = new MetadataApiClient();

    const result = await metadataClient.query({
      findManyApplications: {
        id: true,
        name: true,
        universalIdentifier: true,
      },
    });

    const installedApp = result.findManyApplications.find(
      (app: { universalIdentifier: string }) =>
        app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
    );

    expect(installedApp).toBeDefined();
  });
});
```

## تشغيل الاختبارات

تأكّد من تشغيل خادم Twenty المحلي لديك، ثم:

```bash filename="Terminal" theme={null}
yarn test
```

أو في وضع المراقبة أثناء التطوير:

```bash filename="Terminal" theme={null}
yarn test:watch
```

## التحقق من الأنواع

يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات:

```bash filename="Terminal" theme={null}
yarn twenty dev:typecheck
```

يشغِّل هذا الأمر `tsc --noEmit` على ملف `tsconfig.json` الخاص بتطبيقك ويبلغ عن أي أخطاء في الأنواع. كما تتضمن التطبيقات المُنشأة بالهيكل برنامج نصي `yarn typecheck` يشمل ملفات الاختبار (`tsconfig.spec.json`) أيضًا.

## التكامل المستمر (CI) باستخدام GitHub Actions

تولّد أداة إنشاء الهيكل سير عمل جاهزًا للاستخدام في `.github/workflows/ci.yml`. عند كل دفع إلى الفرع `main` وكل طلب سحب، تُنشئ الأداة خادم Twenty مؤقتًا في بيئة التشغيل (عبر الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`)، ثم تشغِّل الأوامر `yarn lint` و`yarn typecheck` و`yarn test:unit` و`yarn test` مع ضبط المتغيرين `TWENTY_API_URL` و`TWENTY_API_KEY` للإشارة إلى ذلك الخادم. لا تُطلَب أي أسرار، ويمكنك تثبيت إصدار الخادم عبر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل.

راجع قسم [النشر → التكامل/التسليم المستمران الآليان](/l/ar/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) للاطلاع على شرح كامل لثلاثة مسارات العمل التي تم إنشاؤها تلقائياً (`ci.yml`، وخط أنابيب النشر `cd.yml`، و`publish.yml` للنشر على npm).
