Skip to content

Commit df80a71

Browse files
committed
Fix tests so they work in node
1 parent c5f08a9 commit df80a71

File tree

9 files changed

+71
-17
lines changed

9 files changed

+71
-17
lines changed

packages/ai/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"test:ci": "yarn testsetup && node ../../scripts/run_tests_in_ci.js -s test",
4040
"test:skip-clone": "karma start",
4141
"test:browser": "yarn testsetup && karma start",
42+
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register --require src/index.node.ts 'src/**/!(chrome)*.test.ts' --config ../../config/mocharc.node.js",
4243
"test:integration": "karma start --integration",
44+
"test:integration:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha integration/**/*.test.ts --config ../../config/mocharc.node.js",
4345
"api-report": "api-extractor run --local --verbose",
4446
"typings:public": "node ../../scripts/build/use_typings.js ./dist/ai-public.d.ts",
4547
"trusted-type-check": "tsec -p tsconfig.json --noEmit"

packages/ai/src/api.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ import { AIError } from './errors';
3232
import { AIModel, GenerativeModel, ImagenModel } from './models';
3333
import { encodeInstanceIdentifier } from './helpers';
3434
import { GoogleAIBackend } from './backend';
35-
import { ChromeAdapterImpl } from './methods/chrome-adapter';
36-
import { LanguageModel } from './types/language-model';
3735

3836
export { ChatSession } from './methods/chat-session';
3937
export * from './requests/schema-builder';
@@ -124,15 +122,17 @@ export function getGenerativeModel(
124122
`Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`
125123
);
126124
}
127-
let chromeAdapter: ChromeAdapterImpl | undefined;
128-
// Do not initialize a ChromeAdapter if we are not in hybrid mode.
129-
if (typeof window !== 'undefined' && hybridParams.mode) {
130-
chromeAdapter = new ChromeAdapterImpl(
131-
window.LanguageModel as LanguageModel,
132-
hybridParams.mode,
133-
hybridParams.onDeviceParams
134-
);
135-
}
125+
126+
/**
127+
* An AIService registered by index.node.ts will not have a
128+
* chromeAdapterFactory() method.
129+
*/
130+
const chromeAdapter = (ai as AIService).chromeAdapterFactory?.(
131+
hybridParams.mode,
132+
typeof window && window,
133+
hybridParams.onDeviceParams
134+
);
135+
136136
return new GenerativeModel(ai, inCloudParams, requestOptions, chromeAdapter);
137137
}
138138

packages/ai/src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ import { name, version } from '../package.json';
3434
import { decodeInstanceIdentifier } from './helpers';
3535
import { AIError } from './api';
3636
import { AIErrorCode } from './types';
37+
import { chromeAdapterFactory } from './methods/chrome-adapter';
38+
import { LanguageModel } from './types/language-model';
3739

3840
declare global {
3941
interface Window {
40-
[key: string]: unknown;
42+
languageModel: LanguageModel;
4143
}
4244
}
4345

@@ -58,7 +60,14 @@ export function factory(
5860
const app = container.getProvider('app').getImmediate();
5961
const auth = container.getProvider('auth-internal');
6062
const appCheckProvider = container.getProvider('app-check-internal');
61-
return new AIService(app, backend, auth, appCheckProvider);
63+
64+
return new AIService(
65+
app,
66+
backend,
67+
auth,
68+
appCheckProvider,
69+
chromeAdapterFactory
70+
);
6271
}
6372

6473
function registerAI(): void {

packages/ai/src/methods/chrome-adapter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,21 @@ export class ChromeAdapterImpl implements ChromeAdapter {
372372
} as Response;
373373
}
374374
}
375+
376+
/**
377+
* Creates a ChromeAdapterImpl on demand.
378+
*/
379+
export function chromeAdapterFactory(
380+
mode: InferenceMode,
381+
window?: Global | Window,
382+
params?: OnDeviceParams
383+
): ChromeAdapterImpl | undefined {
384+
// Do not initialize a ChromeAdapter if we are not in hybrid mode.
385+
if (typeof window !== 'undefined' && mode) {
386+
return new ChromeAdapterImpl(
387+
(window as Window).languageModel as LanguageModel,
388+
mode,
389+
params
390+
);
391+
}
392+
}

packages/ai/src/service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import { FirebaseApp, _FirebaseService } from '@firebase/app';
19-
import { AI, AIOptions } from './public-types';
19+
import { AI, AIOptions, InferenceMode, OnDeviceParams } from './public-types';
2020
import {
2121
AppCheckInternalComponentName,
2222
FirebaseAppCheckInternal
@@ -27,6 +27,7 @@ import {
2727
FirebaseAuthInternalName
2828
} from '@firebase/auth-interop-types';
2929
import { Backend, VertexAIBackend } from './backend';
30+
import { ChromeAdapterImpl } from './methods/chrome-adapter';
3031

3132
export class AIService implements AI, _FirebaseService {
3233
auth: FirebaseAuthInternal | null;
@@ -38,7 +39,12 @@ export class AIService implements AI, _FirebaseService {
3839
public app: FirebaseApp,
3940
public backend: Backend,
4041
authProvider?: Provider<FirebaseAuthInternalName>,
41-
appCheckProvider?: Provider<AppCheckInternalComponentName>
42+
appCheckProvider?: Provider<AppCheckInternalComponentName>,
43+
public chromeAdapterFactory?: (
44+
mode: InferenceMode,
45+
window?: Global | Window,
46+
params?: OnDeviceParams
47+
) => ChromeAdapterImpl | undefined
4248
) {
4349
const appCheck = appCheckProvider?.getImmediate({ optional: true });
4450
const auth = authProvider?.getImmediate({ optional: true });

packages/ai/test-utils/convert-mocks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
const { readdirSync, readFileSync, writeFileSync } = require('node:fs');
2020
const { join } = require('node:path');
2121

22+
type BackendName = import('./types').BackendName; // Import type without triggering ES module detection
23+
2224
const MOCK_RESPONSES_DIR_PATH = join(
2325
__dirname,
2426
'vertexai-sdk-test-data',
2527
'mock-responses'
2628
);
2729
const MOCK_LOOKUP_OUTPUT_PATH = join(__dirname, 'mocks-lookup.ts');
2830

29-
type BackendName = 'vertexAI' | 'googleAI';
30-
3131
const mockDirs: Record<BackendName, string> = {
3232
vertexAI: join(MOCK_RESPONSES_DIR_PATH, 'vertexai'),
3333
googleAI: join(MOCK_RESPONSES_DIR_PATH, 'googleai')

packages/ai/test-utils/mock-response.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { BackendName } from './types';
1819
import { vertexAIMocksLookup, googleAIMocksLookup } from './mocks-lookup';
1920

2021
const mockSetMaps: Record<BackendName, Record<string, string>> = {

packages/ai/test-utils/types.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
export type BackendName = 'vertexAI' | 'googleAI';

0 commit comments

Comments
 (0)