Testing Miniapps

Test business logic through small typed ports and reserve host-backed tests for a real miniapp realm. This keeps unit tests deterministic without copying a private transport.

Inject the Capability You Use

Importing the live sdk object is safe, but reading one of its properties without a host capability session throws. Keeping host access at the surface boundary makes business logic easier to test and prevents accidental property access during test setup.

Define a narrow application port:

import type { MiniAppPlatformApi } from '@theaiplatform/miniapp-sdk/sdk';

export type ChannelReader = Pick<MiniAppPlatformApi['channels'], 'list'>;

export async function visibleChannelNames(
  channels: ChannelReader,
  workspaceId: string,
): Promise<string[]> {
  const result = await channels.list({ workspaceId });
  return result.rooms
    .filter((room) => !room.archived)
    .map((room) => room.title ?? room.roomId);
}

Pass the live capability at the surface boundary and a plain test double in unit tests:

const channels: ChannelReader = {
  async list() {
    return {
      readMode: 'member',
      rooms: [
        {
          roomId: 'room-1',
          title: 'Design review',
          visibility: 'workspace',
          archived: false,
          createdAt: 1,
          updatedAt: 1,
        },
      ],
    };
  },
};

Surface Context Doubles

Create a complete TapFederatedSurfaceMountContext with inert event and authority implementations. Override only the scope field needed by a test.

import type { TapFederatedSurfaceMountContext } from '@theaiplatform/miniapp-sdk/surface';

export function testMountContext(
  overrides: Partial<TapFederatedSurfaceMountContext> = {},
): TapFederatedSurfaceMountContext {
  return {
    packageId: 'tap_pkg_example_test_0001',
    packageNamespace: 'example-test',
    releaseId: 'tap_pkg_example_test_0001@0.1.0',
    installationId: 'installation-test',
    contributionId: 'test-surface',
    instanceId: 'instance-test',
    hostOrigin: 'https://host.example.test',
    packageAssetBaseUrl: 'https://assets.example.test/targets/desktop/',
    events: {
      publish: async () => undefined,
      subscribe: () => () => undefined,
    },
    hostAuthority: {
      getSnapshot: () => true,
      subscribe: () => () => undefined,
    },
    ...overrides,
  };
}

Test both authority states, missing optional scope, repeated unmount, event unsubscribe, rejected SDK operations, and work settling after unmount.

Theme Helpers

Pass an explicit query string to getMiniAppThemeFromSearch and a spy callback to installMiniAppThemeSync. Always call the returned cleanup function and verify later events have no effect.

Package Tests

Your package CI should:

  1. install dependencies from the public npm registry in a clean checkout;
  2. typecheck and run the complete unit suite;
  3. build every declared target;
  4. assemble and scan the complete package;
  5. validate the emitted descriptor against config-schema.json;
  6. import every declared expose from its emitted graph; and
  7. verify that target and contribution IDs match the source descriptor.

Add a clean consumer test that installs the packed or published SDK without local links or repository-specific configuration.

Host Acceptance

Before pinning the production environment, test the immutable release in The AI Platform through its development environment. Cover initial activation, an update to a new release, compatible state restoration, rollback, and a deliberately failing candidate that leaves the last known good release active.