18d307f52SEvan Baconimport { fs } from 'memfs';
28d307f52SEvan Baconimport path from 'path';
38d307f52SEvan Bacon
48d307f52SEvan Baconimport * as ProjectDevices from '../devices';
58d307f52SEvan Bacon
68d307f52SEvan Bacondescribe('devices info', () => {
78d307f52SEvan Bacon  let projectRoot: string;
88d307f52SEvan Bacon
98d307f52SEvan Bacon  beforeAll(() => {
108d307f52SEvan Bacon    projectRoot = path.join('/', 'tmp', 'xdl-project-settings');
118d307f52SEvan Bacon  });
128d307f52SEvan Bacon
138d307f52SEvan Bacon  afterEach(async () => {
148d307f52SEvan Bacon    await ProjectDevices.setDevicesInfoAsync(projectRoot, { devices: [] });
158d307f52SEvan Bacon  });
168d307f52SEvan Bacon
178d307f52SEvan Bacon  afterAll(() => {
188d307f52SEvan Bacon    if (projectRoot) {
19*e1bb5bdfSKudo Chien      fs.rmdirSync(projectRoot, { recursive: true });
208d307f52SEvan Bacon    }
218d307f52SEvan Bacon  });
228d307f52SEvan Bacon
238d307f52SEvan Bacon  it('should persist device info to disk', async () => {
248d307f52SEvan Bacon    await ProjectDevices.saveDevicesAsync(projectRoot, 'test-device-id');
258d307f52SEvan Bacon
268d307f52SEvan Bacon    const file = path.join(projectRoot, '.expo', 'devices.json');
278d307f52SEvan Bacon    expect(fs.existsSync(file)).toBe(true);
288d307f52SEvan Bacon
298d307f52SEvan Bacon    const { devices } = JSON.parse(fs.readFileSync(file, 'utf8').toString());
308d307f52SEvan Bacon    expect(devices.length).toBe(1);
318d307f52SEvan Bacon    expect(devices[0].installationId).toBe('test-device-id');
328d307f52SEvan Bacon  });
338d307f52SEvan Bacon
348d307f52SEvan Bacon  it('should save an array of devices', async () => {
358d307f52SEvan Bacon    await ProjectDevices.saveDevicesAsync(projectRoot, ['device-id-1', 'device-id-2']);
368d307f52SEvan Bacon    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
378d307f52SEvan Bacon    expect(devices.length).toBe(2);
388d307f52SEvan Bacon    expect(devices.some((device) => device.installationId === 'device-id-1')).toBe(true);
398d307f52SEvan Bacon    expect(devices.some((device) => device.installationId === 'device-id-2')).toBe(true);
408d307f52SEvan Bacon  });
418d307f52SEvan Bacon
428d307f52SEvan Bacon  it('should save at most 10 devices', async () => {
438d307f52SEvan Bacon    const deviceIds = [];
448d307f52SEvan Bacon    for (let i = 0; i < 11; i++) {
458d307f52SEvan Bacon      deviceIds.push(`device-id-${i}`);
468d307f52SEvan Bacon    }
478d307f52SEvan Bacon    await ProjectDevices.saveDevicesAsync(projectRoot, deviceIds);
488d307f52SEvan Bacon    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
498d307f52SEvan Bacon    expect(devices.length).toBe(10);
508d307f52SEvan Bacon  });
518d307f52SEvan Bacon
528d307f52SEvan Bacon  it('should remove older devices if the total number exceeds 10', async () => {
538d307f52SEvan Bacon    const currentTime = new Date().getTime();
548d307f52SEvan Bacon    const earlierTime = currentTime - 10;
558d307f52SEvan Bacon    const earliestTime = currentTime - 20;
568d307f52SEvan Bacon
578d307f52SEvan Bacon    const devicesInfo = [{ installationId: 'oldest-device', lastUsed: earliestTime }];
588d307f52SEvan Bacon    for (let i = 0; i < 9; i++) {
598d307f52SEvan Bacon      devicesInfo.push({ installationId: `device-id-${i}`, lastUsed: earlierTime });
608d307f52SEvan Bacon    }
618d307f52SEvan Bacon    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
628d307f52SEvan Bacon      devices: devicesInfo,
638d307f52SEvan Bacon    });
648d307f52SEvan Bacon
658d307f52SEvan Bacon    await ProjectDevices.saveDevicesAsync(projectRoot, 'newest-device');
668d307f52SEvan Bacon    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
678d307f52SEvan Bacon    expect(devices.length).toBe(10);
688d307f52SEvan Bacon    expect(devices[0].installationId).toBe('newest-device');
698d307f52SEvan Bacon    expect(devices.some((device) => device.installationId === 'oldest-device')).toBe(false);
708d307f52SEvan Bacon  });
718d307f52SEvan Bacon
728d307f52SEvan Bacon  it('should remove any devices last used before 30 days ago', async () => {
738d307f52SEvan Bacon    const currentTime = new Date().getTime();
748d307f52SEvan Bacon    const time30DaysAnd1SecondAgo = currentTime - 30 * 24 * 60 * 60 * 1000 - 1000;
758d307f52SEvan Bacon    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
768d307f52SEvan Bacon      devices: [{ installationId: 'very-old-device-id', lastUsed: time30DaysAnd1SecondAgo }],
778d307f52SEvan Bacon    });
788d307f52SEvan Bacon    await ProjectDevices.saveDevicesAsync(projectRoot, 'new-device-id');
798d307f52SEvan Bacon    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
808d307f52SEvan Bacon    expect(devices.length).toBe(1);
818d307f52SEvan Bacon    expect(devices[0].installationId).toBe('new-device-id');
828d307f52SEvan Bacon  });
838d307f52SEvan Bacon
848d307f52SEvan Bacon  it('should remove old devices when reading for the first time from disk', async () => {
858d307f52SEvan Bacon    const currentTime = new Date().getTime();
868d307f52SEvan Bacon    const time30DaysAnd1SecondAgo = currentTime - 30 * 24 * 60 * 60 * 1000 - 1000;
878d307f52SEvan Bacon    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
888d307f52SEvan Bacon      devices: [
898d307f52SEvan Bacon        { installationId: 'very-old-device-id', lastUsed: time30DaysAnd1SecondAgo },
908d307f52SEvan Bacon        { installationId: 'new-device-id', lastUsed: currentTime },
918d307f52SEvan Bacon      ],
928d307f52SEvan Bacon    });
938d307f52SEvan Bacon    // use readDeviceInfoAsync to bypass memoized devices and read from disk
948d307f52SEvan Bacon    const { devices } = await ProjectDevices.readDevicesInfoAsync(projectRoot);
958d307f52SEvan Bacon    expect(devices.length).toBe(1);
968d307f52SEvan Bacon    expect(devices[0].installationId).toBe('new-device-id');
978d307f52SEvan Bacon  });
988d307f52SEvan Bacon});
99