Add comprehensive Home Assistant WebSocket and API tests
- Created detailed test suite for Home Assistant WebSocket client - Implemented tests for WebSocket connection, authentication, and error handling - Added comprehensive test coverage for HassInstanceImpl API methods - Mocked WebSocket and fetch to simulate various connection scenarios - Covered authentication, state retrieval, service calls, and environment configuration - Improved test infrastructure for Home Assistant integration
This commit is contained in:
265
__tests__/hass/index.test.ts
Normal file
265
__tests__/hass/index.test.ts
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals';
|
||||||
|
import { WebSocket } from 'ws';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
// Define WebSocket mock types
|
||||||
|
type WebSocketCallback = (...args: any[]) => void;
|
||||||
|
type WebSocketEventHandler = (event: string, callback: WebSocketCallback) => void;
|
||||||
|
type WebSocketSendHandler = (data: string) => void;
|
||||||
|
type WebSocketCloseHandler = () => void;
|
||||||
|
|
||||||
|
type WebSocketMock = {
|
||||||
|
on: jest.MockedFunction<WebSocketEventHandler>;
|
||||||
|
send: jest.MockedFunction<WebSocketSendHandler>;
|
||||||
|
close: jest.MockedFunction<WebSocketCloseHandler>;
|
||||||
|
readyState: number;
|
||||||
|
OPEN: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock WebSocket
|
||||||
|
jest.mock('ws', () => {
|
||||||
|
return {
|
||||||
|
WebSocket: jest.fn().mockImplementation(() => ({
|
||||||
|
on: jest.fn(),
|
||||||
|
send: jest.fn(),
|
||||||
|
close: jest.fn(),
|
||||||
|
readyState: 1,
|
||||||
|
OPEN: 1,
|
||||||
|
removeAllListeners: jest.fn()
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock fetch globally
|
||||||
|
const mockFetch = jest.fn() as jest.MockedFunction<typeof fetch>;
|
||||||
|
global.fetch = mockFetch;
|
||||||
|
|
||||||
|
describe('Home Assistant Integration', () => {
|
||||||
|
describe('HassWebSocketClient', () => {
|
||||||
|
let client: any;
|
||||||
|
const mockUrl = 'ws://localhost:8123/api/websocket';
|
||||||
|
const mockToken = 'test_token';
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const { HassWebSocketClient } = await import('../../src/hass/index.js');
|
||||||
|
client = new HassWebSocketClient(mockUrl, mockToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a WebSocket client with the provided URL and token', () => {
|
||||||
|
expect(client).toBeInstanceOf(EventEmitter);
|
||||||
|
expect(WebSocket).toHaveBeenCalledWith(mockUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should connect and authenticate successfully', async () => {
|
||||||
|
const mockWs = (WebSocket as jest.MockedClass<typeof WebSocket>).mock.results[0].value as unknown as WebSocketMock;
|
||||||
|
const connectPromise = client.connect();
|
||||||
|
|
||||||
|
// Get and call the open callback
|
||||||
|
const openCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'open');
|
||||||
|
if (!openCallEntry) throw new Error('Open callback not found');
|
||||||
|
const openCallback = openCallEntry[1];
|
||||||
|
openCallback();
|
||||||
|
|
||||||
|
// Verify authentication message
|
||||||
|
expect(mockWs.send).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'auth',
|
||||||
|
access_token: mockToken
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get and call the message callback
|
||||||
|
const messageCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'message');
|
||||||
|
if (!messageCallEntry) throw new Error('Message callback not found');
|
||||||
|
const messageCallback = messageCallEntry[1];
|
||||||
|
messageCallback(JSON.stringify({ type: 'auth_ok' }));
|
||||||
|
|
||||||
|
await connectPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle authentication failure', async () => {
|
||||||
|
const mockWs = (WebSocket as jest.MockedClass<typeof WebSocket>).mock.results[0].value as unknown as WebSocketMock;
|
||||||
|
const connectPromise = client.connect();
|
||||||
|
|
||||||
|
// Get and call the open callback
|
||||||
|
const openCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'open');
|
||||||
|
if (!openCallEntry) throw new Error('Open callback not found');
|
||||||
|
const openCallback = openCallEntry[1];
|
||||||
|
openCallback();
|
||||||
|
|
||||||
|
// Get and call the message callback with auth failure
|
||||||
|
const messageCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'message');
|
||||||
|
if (!messageCallEntry) throw new Error('Message callback not found');
|
||||||
|
const messageCallback = messageCallEntry[1];
|
||||||
|
messageCallback(JSON.stringify({ type: 'auth_invalid' }));
|
||||||
|
|
||||||
|
await expect(connectPromise).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle connection errors', async () => {
|
||||||
|
const mockWs = (WebSocket as jest.MockedClass<typeof WebSocket>).mock.results[0].value as unknown as WebSocketMock;
|
||||||
|
const connectPromise = client.connect();
|
||||||
|
|
||||||
|
// Get and call the error callback
|
||||||
|
const errorCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'error');
|
||||||
|
if (!errorCallEntry) throw new Error('Error callback not found');
|
||||||
|
const errorCallback = errorCallEntry[1];
|
||||||
|
errorCallback(new Error('Connection failed'));
|
||||||
|
|
||||||
|
await expect(connectPromise).rejects.toThrow('Connection failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle message parsing errors', async () => {
|
||||||
|
const mockWs = (WebSocket as jest.MockedClass<typeof WebSocket>).mock.results[0].value as unknown as WebSocketMock;
|
||||||
|
const connectPromise = client.connect();
|
||||||
|
|
||||||
|
// Get and call the open callback
|
||||||
|
const openCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'open');
|
||||||
|
if (!openCallEntry) throw new Error('Open callback not found');
|
||||||
|
const openCallback = openCallEntry[1];
|
||||||
|
openCallback();
|
||||||
|
|
||||||
|
// Get and call the message callback with invalid JSON
|
||||||
|
const messageCallEntry = mockWs.on.mock.calls.find(call => call[0] === 'message');
|
||||||
|
if (!messageCallEntry) throw new Error('Message callback not found');
|
||||||
|
const messageCallback = messageCallEntry[1];
|
||||||
|
|
||||||
|
// Should emit error event
|
||||||
|
await expect(new Promise((resolve) => {
|
||||||
|
client.once('error', resolve);
|
||||||
|
messageCallback('invalid json');
|
||||||
|
})).resolves.toBeInstanceOf(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HassInstanceImpl', () => {
|
||||||
|
let instance: any;
|
||||||
|
const mockBaseUrl = 'http://localhost:8123';
|
||||||
|
const mockToken = 'test_token';
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const { HassInstanceImpl } = await import('../../src/hass/index.js');
|
||||||
|
instance = new HassInstanceImpl(mockBaseUrl, mockToken);
|
||||||
|
mockFetch.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance with the provided URL and token', () => {
|
||||||
|
expect(instance.baseUrl).toBe(mockBaseUrl);
|
||||||
|
expect(instance.token).toBe(mockToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch states successfully', async () => {
|
||||||
|
const mockStates = [
|
||||||
|
{
|
||||||
|
entity_id: 'light.living_room',
|
||||||
|
state: 'on',
|
||||||
|
attributes: {}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockStates
|
||||||
|
} as Response);
|
||||||
|
|
||||||
|
const states = await instance.fetchStates();
|
||||||
|
expect(states).toEqual(mockStates);
|
||||||
|
expect(mockFetch).toHaveBeenCalledWith(
|
||||||
|
`${mockBaseUrl}/api/states`,
|
||||||
|
expect.objectContaining({
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${mockToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch single entity state successfully', async () => {
|
||||||
|
const mockState = {
|
||||||
|
entity_id: 'light.living_room',
|
||||||
|
state: 'on',
|
||||||
|
attributes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => mockState
|
||||||
|
} as Response);
|
||||||
|
|
||||||
|
const state = await instance.fetchState('light.living_room');
|
||||||
|
expect(state).toEqual(mockState);
|
||||||
|
expect(mockFetch).toHaveBeenCalledWith(
|
||||||
|
`${mockBaseUrl}/api/states/light.living_room`,
|
||||||
|
expect.objectContaining({
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${mockToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call service successfully', async () => {
|
||||||
|
mockFetch.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({})
|
||||||
|
} as Response);
|
||||||
|
|
||||||
|
await instance.callService('light', 'turn_on', { entity_id: 'light.living_room' });
|
||||||
|
expect(mockFetch).toHaveBeenCalledWith(
|
||||||
|
`${mockBaseUrl}/api/services/light/turn_on`,
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${mockToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ entity_id: 'light.living_room' })
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get_hass', () => {
|
||||||
|
const originalEnv = process.env;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
process.env = { ...originalEnv };
|
||||||
|
process.env.HASS_HOST = 'http://localhost:8123';
|
||||||
|
process.env.HASS_TOKEN = 'test_token';
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = originalEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a development instance by default', async () => {
|
||||||
|
const { get_hass } = await import('../../src/hass/index.js');
|
||||||
|
const instance = await get_hass();
|
||||||
|
expect(instance.baseUrl).toBe('http://localhost:8123');
|
||||||
|
expect(instance.token).toBe('test_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a test instance when specified', async () => {
|
||||||
|
const { get_hass } = await import('../../src/hass/index.js');
|
||||||
|
const instance = await get_hass('test');
|
||||||
|
expect(instance.baseUrl).toBe('http://localhost:8123');
|
||||||
|
expect(instance.token).toBe('test_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a production instance when specified', async () => {
|
||||||
|
process.env.HASS_HOST = 'https://hass.example.com';
|
||||||
|
process.env.HASS_TOKEN = 'prod_token';
|
||||||
|
|
||||||
|
const { get_hass } = await import('../../src/hass/index.js');
|
||||||
|
const instance = await get_hass('production');
|
||||||
|
expect(instance.baseUrl).toBe('https://hass.example.com');
|
||||||
|
expect(instance.token).toBe('prod_token');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -23,30 +23,30 @@
|
|||||||
<div class='clearfix'>
|
<div class='clearfix'>
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">9.44% </span>
|
<span class="strong">13.36% </span>
|
||||||
<span class="quiet">Statements</span>
|
<span class="quiet">Statements</span>
|
||||||
<span class='fraction'>111/1175</span>
|
<span class='fraction'>157/1175</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">5.65% </span>
|
<span class="strong">8.59% </span>
|
||||||
<span class="quiet">Branches</span>
|
<span class="quiet">Branches</span>
|
||||||
<span class='fraction'>25/442</span>
|
<span class='fraction'>38/442</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">10.61% </span>
|
<span class="strong">13.71% </span>
|
||||||
<span class="quiet">Functions</span>
|
<span class="quiet">Functions</span>
|
||||||
<span class='fraction'>24/226</span>
|
<span class='fraction'>31/226</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
<div class='fl pad1y space-right2'>
|
||||||
<span class="strong">9.52% </span>
|
<span class="strong">13.54% </span>
|
||||||
<span class="quiet">Lines</span>
|
<span class="quiet">Lines</span>
|
||||||
<span class='fraction'>109/1144</span>
|
<span class='fraction'>155/1144</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -154,18 +154,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="file low" data-value="src/config"><a href="src/config/index.html">src/config</a></td>
|
<td class="file high" data-value="src/config"><a href="src/config/index.html">src/config</a></td>
|
||||||
<td data-value="0" class="pic low">
|
<td data-value="100" class="pic high">
|
||||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
||||||
</td>
|
</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="100" class="pct high">100%</td>
|
||||||
<td data-value="2" class="abs low">0/2</td>
|
<td data-value="2" class="abs high">2/2</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="50" class="pct high">50%</td>
|
||||||
<td data-value="8" class="abs low">0/8</td>
|
<td data-value="8" class="abs high">4/8</td>
|
||||||
<td data-value="100" class="pct high">100%</td>
|
<td data-value="100" class="pct high">100%</td>
|
||||||
<td data-value="0" class="abs high">0/0</td>
|
<td data-value="0" class="abs high">0/0</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="100" class="pct high">100%</td>
|
||||||
<td data-value="2" class="abs low">0/2</td>
|
<td data-value="2" class="abs high">2/2</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
@@ -185,17 +185,17 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="file low" data-value="src/hass"><a href="src/hass/index.html">src/hass</a></td>
|
<td class="file low" data-value="src/hass"><a href="src/hass/index.html">src/hass</a></td>
|
||||||
<td data-value="0" class="pic low">
|
<td data-value="39.63" class="pic low">
|
||||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
<div class="chart"><div class="cover-fill" style="width: 39%"></div><div class="cover-empty" style="width: 61%"></div></div>
|
||||||
</td>
|
</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="39.63" class="pct low">39.63%</td>
|
||||||
<td data-value="111" class="abs low">0/111</td>
|
<td data-value="111" class="abs low">44/111</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="21.95" class="pct low">21.95%</td>
|
||||||
<td data-value="41" class="abs low">0/41</td>
|
<td data-value="41" class="abs low">9/41</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="30.43" class="pct low">30.43%</td>
|
||||||
<td data-value="23" class="abs low">0/23</td>
|
<td data-value="23" class="abs low">7/23</td>
|
||||||
<td data-value="0" class="pct low">0%</td>
|
<td data-value="39.63" class="pct low">39.63%</td>
|
||||||
<td data-value="111" class="abs low">0/111</td>
|
<td data-value="111" class="abs low">44/111</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2025-01-30T19:05:31.249Z
|
at 2025-01-30T19:28:22.554Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -927,20 +927,20 @@ TN:
|
|||||||
SF:src/config/hass.config.ts
|
SF:src/config/hass.config.ts
|
||||||
FNF:0
|
FNF:0
|
||||||
FNH:0
|
FNH:0
|
||||||
DA:4,0
|
DA:4,1
|
||||||
DA:6,0
|
DA:6,1
|
||||||
LF:2
|
LF:2
|
||||||
LH:0
|
LH:2
|
||||||
BRDA:7,0,0,0
|
BRDA:7,0,0,1
|
||||||
BRDA:7,0,1,0
|
BRDA:7,0,1,0
|
||||||
BRDA:8,1,0,0
|
BRDA:8,1,0,1
|
||||||
BRDA:8,1,1,0
|
BRDA:8,1,1,0
|
||||||
BRDA:9,2,0,0
|
BRDA:9,2,0,1
|
||||||
BRDA:9,2,1,0
|
BRDA:9,2,1,0
|
||||||
BRDA:10,3,0,0
|
BRDA:10,3,0,1
|
||||||
BRDA:10,3,1,0
|
BRDA:10,3,1,0
|
||||||
BRF:8
|
BRF:8
|
||||||
BRH:0
|
BRH:4
|
||||||
end_of_record
|
end_of_record
|
||||||
TN:
|
TN:
|
||||||
SF:src/context/index.ts
|
SF:src/context/index.ts
|
||||||
@@ -1172,9 +1172,9 @@ FN:400,(anonymous_20)
|
|||||||
FN:412,(anonymous_21)
|
FN:412,(anonymous_21)
|
||||||
FN:421,get_hass
|
FN:421,get_hass
|
||||||
FNF:23
|
FNF:23
|
||||||
FNH:0
|
FNH:7
|
||||||
FNDA:0,(anonymous_0)
|
FNDA:0,(anonymous_0)
|
||||||
FNDA:0,(anonymous_1)
|
FNDA:5,(anonymous_1)
|
||||||
FNDA:0,(anonymous_2)
|
FNDA:0,(anonymous_2)
|
||||||
FNDA:0,(anonymous_3)
|
FNDA:0,(anonymous_3)
|
||||||
FNDA:0,(anonymous_4)
|
FNDA:0,(anonymous_4)
|
||||||
@@ -1188,25 +1188,25 @@ FNDA:0,(anonymous_11)
|
|||||||
FNDA:0,(anonymous_12)
|
FNDA:0,(anonymous_12)
|
||||||
FNDA:0,(anonymous_13)
|
FNDA:0,(anonymous_13)
|
||||||
FNDA:0,(anonymous_14)
|
FNDA:0,(anonymous_14)
|
||||||
FNDA:0,(anonymous_15)
|
FNDA:5,(anonymous_15)
|
||||||
FNDA:0,(anonymous_16)
|
FNDA:5,(anonymous_16)
|
||||||
FNDA:0,(anonymous_17)
|
FNDA:1,(anonymous_17)
|
||||||
FNDA:0,(anonymous_18)
|
FNDA:1,(anonymous_18)
|
||||||
FNDA:0,(anonymous_19)
|
FNDA:1,(anonymous_19)
|
||||||
FNDA:0,(anonymous_20)
|
FNDA:0,(anonymous_20)
|
||||||
FNDA:0,(anonymous_21)
|
FNDA:0,(anonymous_21)
|
||||||
FNDA:0,get_hass
|
FNDA:3,get_hass
|
||||||
DA:89,0
|
DA:89,1
|
||||||
DA:101,0
|
DA:101,0
|
||||||
DA:143,0
|
DA:143,1
|
||||||
DA:159,0
|
DA:159,5
|
||||||
DA:160,0
|
DA:160,5
|
||||||
DA:161,0
|
DA:161,5
|
||||||
DA:162,0
|
DA:162,5
|
||||||
DA:170,0
|
DA:170,5
|
||||||
DA:171,0
|
DA:171,5
|
||||||
DA:174,0
|
DA:174,5
|
||||||
DA:175,0
|
DA:175,5
|
||||||
DA:184,0
|
DA:184,0
|
||||||
DA:185,0
|
DA:185,0
|
||||||
DA:188,0
|
DA:188,0
|
||||||
@@ -1262,32 +1262,32 @@ DA:286,0
|
|||||||
DA:291,0
|
DA:291,0
|
||||||
DA:292,0
|
DA:292,0
|
||||||
DA:293,0
|
DA:293,0
|
||||||
DA:333,0
|
DA:333,5
|
||||||
DA:334,0
|
DA:334,5
|
||||||
DA:335,0
|
DA:335,5
|
||||||
DA:340,0
|
DA:340,5
|
||||||
DA:341,0
|
DA:341,5
|
||||||
DA:342,0
|
DA:342,5
|
||||||
DA:343,0
|
DA:343,5
|
||||||
DA:344,0
|
DA:344,5
|
||||||
DA:345,0
|
DA:345,5
|
||||||
DA:346,0
|
DA:346,5
|
||||||
DA:347,0
|
DA:347,5
|
||||||
DA:348,0
|
DA:348,5
|
||||||
DA:349,0
|
DA:349,5
|
||||||
DA:350,0
|
DA:350,5
|
||||||
DA:354,0
|
DA:354,1
|
||||||
DA:361,0
|
DA:361,1
|
||||||
DA:362,0
|
DA:362,0
|
||||||
DA:365,0
|
DA:365,1
|
||||||
DA:366,0
|
DA:366,1
|
||||||
DA:370,0
|
DA:370,1
|
||||||
DA:377,0
|
DA:377,1
|
||||||
DA:378,0
|
DA:378,0
|
||||||
DA:381,0
|
DA:381,1
|
||||||
DA:382,0
|
DA:382,1
|
||||||
DA:386,0
|
DA:386,1
|
||||||
DA:395,0
|
DA:395,1
|
||||||
DA:396,0
|
DA:396,0
|
||||||
DA:401,0
|
DA:401,0
|
||||||
DA:402,0
|
DA:402,0
|
||||||
@@ -1295,31 +1295,31 @@ DA:406,0
|
|||||||
DA:409,0
|
DA:409,0
|
||||||
DA:413,0
|
DA:413,0
|
||||||
DA:414,0
|
DA:414,0
|
||||||
DA:419,0
|
DA:419,1
|
||||||
DA:422,0
|
DA:422,3
|
||||||
DA:423,0
|
DA:423,2
|
||||||
DA:424,0
|
DA:424,2
|
||||||
DA:427,0
|
DA:427,1
|
||||||
DA:429,0
|
DA:429,1
|
||||||
DA:430,0
|
DA:430,0
|
||||||
DA:431,0
|
DA:431,0
|
||||||
DA:434,0
|
DA:434,1
|
||||||
DA:435,0
|
DA:435,1
|
||||||
DA:436,0
|
DA:436,1
|
||||||
DA:438,0
|
DA:438,1
|
||||||
LF:111
|
LF:111
|
||||||
LH:0
|
LH:44
|
||||||
BRDA:101,0,0,0
|
BRDA:101,0,0,0
|
||||||
BRDA:101,0,1,0
|
BRDA:101,0,1,0
|
||||||
BRDA:145,1,0,0
|
BRDA:145,1,0,1
|
||||||
BRDA:145,1,1,0
|
BRDA:145,1,1,0
|
||||||
BRDA:146,2,0,0
|
BRDA:146,2,0,1
|
||||||
BRDA:146,2,1,0
|
BRDA:146,2,1,0
|
||||||
BRDA:149,3,0,0
|
BRDA:149,3,0,1
|
||||||
BRDA:149,3,1,0
|
BRDA:149,3,1,0
|
||||||
BRDA:150,4,0,0
|
BRDA:150,4,0,1
|
||||||
BRDA:150,4,1,0
|
BRDA:150,4,1,0
|
||||||
BRDA:172,5,0,0
|
BRDA:172,5,0,5
|
||||||
BRDA:184,6,0,0
|
BRDA:184,6,0,0
|
||||||
BRDA:184,7,0,0
|
BRDA:184,7,0,0
|
||||||
BRDA:184,7,1,0
|
BRDA:184,7,1,0
|
||||||
@@ -1345,13 +1345,13 @@ BRDA:377,19,0,0
|
|||||||
BRDA:395,20,0,0
|
BRDA:395,20,0,0
|
||||||
BRDA:401,21,0,0
|
BRDA:401,21,0,0
|
||||||
BRDA:413,22,0,0
|
BRDA:413,22,0,0
|
||||||
BRDA:421,23,0,0
|
BRDA:421,23,0,1
|
||||||
BRDA:422,24,0,0
|
BRDA:422,24,0,2
|
||||||
BRDA:429,25,0,0
|
BRDA:429,25,0,0
|
||||||
BRDA:429,26,0,0
|
BRDA:429,26,0,1
|
||||||
BRDA:429,26,1,0
|
BRDA:429,26,1,1
|
||||||
BRF:41
|
BRF:41
|
||||||
BRH:0
|
BRH:9
|
||||||
end_of_record
|
end_of_record
|
||||||
TN:
|
TN:
|
||||||
SF:src/performance/index.ts
|
SF:src/performance/index.ts
|
||||||
|
|||||||
@@ -6,15 +6,28 @@ module.exports = (request, options) => {
|
|||||||
return path.resolve(__dirname, 'node_modules', request.replace('#', ''));
|
return path.resolve(__dirname, 'node_modules', request.replace('#', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle .js extensions for TypeScript files
|
||||||
|
if (request.endsWith('.js')) {
|
||||||
|
const tsRequest = request.replace(/\.js$/, '.ts');
|
||||||
|
try {
|
||||||
|
return options.defaultResolver(tsRequest, options);
|
||||||
|
} catch (e) {
|
||||||
|
// If the .ts file doesn't exist, continue with the original request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call the default resolver
|
// Call the default resolver
|
||||||
return options.defaultResolver(request, {
|
return options.defaultResolver(request, {
|
||||||
...options,
|
...options,
|
||||||
// Force node to resolve modules as CommonJS
|
// Handle ESM modules
|
||||||
packageFilter: pkg => {
|
packageFilter: pkg => {
|
||||||
if (pkg.type === 'module') {
|
// Preserve ESM modules
|
||||||
pkg.type = 'commonjs';
|
if (pkg.type === 'module' && pkg.exports) {
|
||||||
if (pkg.exports && pkg.exports.import) {
|
// If there's a specific export for the current conditions, use that
|
||||||
|
if (pkg.exports.import) {
|
||||||
pkg.main = pkg.exports.import;
|
pkg.main = pkg.exports.import;
|
||||||
|
} else if (typeof pkg.exports === 'string') {
|
||||||
|
pkg.main = pkg.exports;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pkg;
|
return pkg;
|
||||||
|
|||||||
@@ -5,29 +5,30 @@ module.exports = {
|
|||||||
extensionsToTreatAsEsm: ['.ts'],
|
extensionsToTreatAsEsm: ['.ts'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||||
'#(.*)': '<rootDir>/node_modules/$1',
|
|
||||||
'^(\\.{1,2}/.*)\\.ts$': '$1',
|
'^(\\.{1,2}/.*)\\.ts$': '$1',
|
||||||
'^chalk$': 'chalk',
|
'^chalk$': '<rootDir>/node_modules/chalk/source/index.js',
|
||||||
'#ansi-styles': 'ansi-styles',
|
'#ansi-styles': '<rootDir>/node_modules/ansi-styles/index.js',
|
||||||
'#supports-color': 'supports-color'
|
'#supports-color': '<rootDir>/node_modules/supports-color/index.js'
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.tsx?$': [
|
'^.+\\.tsx?$': [
|
||||||
'ts-jest',
|
'ts-jest',
|
||||||
{
|
{
|
||||||
useESM: true,
|
useESM: true,
|
||||||
|
tsconfig: 'tsconfig.json'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
'node_modules/(?!(@digital-alchemy|chalk|ansi-styles|supports-color)/)'
|
'node_modules/(?!(@digital-alchemy|chalk|ansi-styles|supports-color)/.*)'
|
||||||
],
|
],
|
||||||
resolver: '<rootDir>/jest-resolver.cjs',
|
resolver: '<rootDir>/jest-resolver.cjs',
|
||||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
testMatch: [
|
testMatch: [
|
||||||
'**/__tests__/helpers.test.ts',
|
'**/__tests__/helpers.test.ts',
|
||||||
'**/__tests__/schemas/devices.test.ts',
|
'**/__tests__/schemas/devices.test.ts',
|
||||||
'**/__tests__/context/index.test.ts'
|
'**/__tests__/context/index.test.ts',
|
||||||
|
'**/__tests__/hass/index.test.ts'
|
||||||
],
|
],
|
||||||
globals: {
|
globals: {
|
||||||
'ts-jest': {
|
'ts-jest': {
|
||||||
|
|||||||
@@ -50,28 +50,28 @@ jest.mock('ws', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Mock chalk
|
// Mock chalk
|
||||||
jest.mock('chalk', () => ({
|
const createChalkMock = () => {
|
||||||
default: {
|
const handler = {
|
||||||
red: (text: string) => text,
|
get(target: any, prop: string) {
|
||||||
green: (text: string) => text,
|
if (prop === 'default') {
|
||||||
yellow: (text: string) => text,
|
return createChalkMock();
|
||||||
blue: (text: string) => text,
|
|
||||||
magenta: (text: string) => text,
|
|
||||||
cyan: (text: string) => text,
|
|
||||||
white: (text: string) => text,
|
|
||||||
gray: (text: string) => text,
|
|
||||||
grey: (text: string) => text,
|
|
||||||
black: (text: string) => text,
|
|
||||||
bold: (text: string) => text,
|
|
||||||
dim: (text: string) => text,
|
|
||||||
italic: (text: string) => text,
|
|
||||||
underline: (text: string) => text,
|
|
||||||
inverse: (text: string) => text,
|
|
||||||
hidden: (text: string) => text,
|
|
||||||
strikethrough: (text: string) => text,
|
|
||||||
visible: (text: string) => text,
|
|
||||||
}
|
}
|
||||||
}));
|
return typeof prop === 'string' ? createChalkMock() : target[prop];
|
||||||
|
},
|
||||||
|
apply(target: any, thisArg: any, args: any[]) {
|
||||||
|
return args[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new Proxy(() => { }, handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('chalk', () => createChalkMock());
|
||||||
|
|
||||||
|
// Mock ansi-styles
|
||||||
|
jest.mock('ansi-styles', () => ({}), { virtual: true });
|
||||||
|
|
||||||
|
// Mock supports-color
|
||||||
|
jest.mock('supports-color', () => ({}), { virtual: true });
|
||||||
|
|
||||||
// Reset mocks between tests
|
// Reset mocks between tests
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user