Back to Repositories

Validating Configuration Request Handling in Parcel Bundler

This test suite validates the ConfigRequest functionality in Parcel’s core module, focusing on configuration invalidation and request handling. The tests ensure proper handling of file changes, environment variables, and configuration updates within the bundler’s workflow.

Test Coverage Overview

The test suite provides comprehensive coverage of ConfigRequest functionality, particularly focusing on various invalidation scenarios. Key test cases include file changes, environment variables, option changes, and configuration key modifications.

  • File system operations validation
  • Environment variable change handling
  • Configuration key change detection
  • Build and startup invalidation checks

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern combined with sinon for mocking and spying. The implementation leverages mock APIs and file system abstractions to validate configuration request behavior.

Key patterns include:
  • Mock RunAPI implementation with spy functions
  • MemoryFS usage for file system operations
  • Sinon spy assertions for validation

Technical Details

Testing infrastructure includes:

  • WorkerFarm configuration for parallel processing
  • MemoryFS for file system operations
  • Sinon for spying and mocking
  • Flow type checking integration
  • Custom mock API implementation

Best Practices Demonstrated

The test suite exemplifies several testing best practices for configuration management systems.

  • Isolated test cases with clear scope
  • Comprehensive mock setup and teardown
  • Type-safe testing with Flow integration
  • Granular assertion checking
  • Clear test case organization

parcel-bundler/parcel

packages/core/core/test/requests/ConfigRequest.test.js

            
// @flow strict-local

import WorkerFarm from '@parcel/workers';
import path from 'path';
import assert from 'assert';
import sinon from 'sinon';
import {MemoryFS} from '@parcel/fs';
import {hashString} from '@parcel/rust';

import type {
  ConfigRequest,
  ConfigRequestResult,
} from '../../src/requests/ConfigRequest';
import type {RunAPI} from '../../src/RequestTracker';
import {runConfigRequest} from '../../src/requests/ConfigRequest';
import {toProjectPath} from '../../src/projectPath';

// $FlowFixMe unclear-type forgive me
const mockCast = (f: any): any => f;

describe('ConfigRequest tests', () => {
  const projectRoot = 'project_root';
  const farm = new WorkerFarm({
    workerPath: require.resolve('../../src/worker.js'),
    maxConcurrentWorkers: 1,
  });
  let fs = new MemoryFS(farm);
  beforeEach(() => {
    fs = new MemoryFS(farm);
  });

  const getMockRunApi = (
    options: mixed = {projectRoot, inputFS: fs},
  ): RunAPI<ConfigRequestResult> => {
    const mockRunApi = {
      storeResult: sinon.spy(),
      canSkipSubrequest: sinon.spy(),
      invalidateOnFileCreate: sinon.spy(),
      getInvalidSubRequests: sinon.spy(),
      getInvalidations: sinon.spy(),
      getPreviousResult: sinon.spy(),
      getRequestResult: sinon.spy(),
      getSubRequests: sinon.spy(),
      invalidateOnBuild: sinon.spy(),
      invalidateOnConfigKeyChange: sinon.spy(),
      invalidateOnEnvChange: sinon.spy(),
      invalidateOnFileDelete: sinon.spy(),
      invalidateOnFileUpdate: sinon.spy(),
      invalidateOnOptionChange: sinon.spy(),
      invalidateOnStartup: sinon.spy(),
      runRequest: sinon.spy(request => {
        return request.run({
          api: mockRunApi,
          options,
        });
      }),
    };
    return mockRunApi;
  };

  const baseRequest: ConfigRequest = {
    id: 'config_request_test',
    invalidateOnBuild: false,
    invalidateOnConfigKeyChange: [],
    invalidateOnFileCreate: [],
    invalidateOnEnvChange: new Set(),
    invalidateOnOptionChange: new Set(),
    invalidateOnStartup: false,
    invalidateOnFileChange: new Set(),
  };

  it('can execute a config request', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
    });
  });

  it('forwards "invalidateOnFileChange" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnFileChange: new Set([
        toProjectPath(projectRoot, 'path1'),
        toProjectPath(projectRoot, 'path2'),
      ]),
    });

    assert(
      mockCast(mockRunApi.invalidateOnFileUpdate).called,
      'Invalidate was called',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileUpdate).calledWith('path1'),
      'Invalidate was called with path1',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileUpdate).calledWith('path2'),
      'Invalidate was called with path2',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileDelete).calledWith('path1'),
      'Invalidate was called with path1',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileDelete).calledWith('path2'),
      'Invalidate was called with path2',
    );
  });

  it('forwards "invalidateOnFileCreate" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnFileCreate: [
        {filePath: toProjectPath(projectRoot, 'filePath')},
        {glob: toProjectPath(projectRoot, 'glob')},
        {
          fileName: 'package.json',
          aboveFilePath: toProjectPath(projectRoot, 'fileAbove'),
        },
      ],
    });

    assert(
      mockCast(mockRunApi.invalidateOnFileCreate).called,
      'Invalidate was called',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileCreate).calledWithMatch({
        filePath: 'filePath',
      }),
      'Invalidate was called for path',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileCreate).calledWithMatch({
        glob: 'glob',
      }),
      'Invalidate was called for glob',
    );
    assert(
      mockCast(mockRunApi.invalidateOnFileCreate).calledWithMatch({
        fileName: 'package.json',
        aboveFilePath: 'fileAbove',
      }),
      'Invalidate was called for fileAbove',
    );
  });

  it('forwards "invalidateOnEnvChange" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnEnvChange: new Set(['env1', 'env2']),
    });

    assert(
      mockCast(mockRunApi.invalidateOnEnvChange).called,
      'Invalidate was called',
    );
    assert(
      mockCast(mockRunApi.invalidateOnEnvChange).calledWithMatch('env1'),
      'Invalidate was called for env1',
    );
    assert(
      mockCast(mockRunApi.invalidateOnEnvChange).calledWithMatch('env2'),
      'Invalidate was called for env1',
    );
  });

  it('forwards "invalidateOnOptionChange" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnOptionChange: new Set(['option1', 'option2']),
    });

    assert(
      mockCast(mockRunApi.invalidateOnOptionChange).called,
      'Invalidate was called',
    );
    assert(
      mockCast(mockRunApi.invalidateOnOptionChange).calledWithMatch('option1'),
      'Invalidate was called for option1',
    );
    assert(
      mockCast(mockRunApi.invalidateOnOptionChange).calledWithMatch('option2'),
      'Invalidate was called for option2',
    );
  });

  it('forwards "invalidateOnStartup" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnStartup: true,
    });

    assert(
      mockCast(mockRunApi.invalidateOnStartup).called,
      'Invalidate was called',
    );
  });

  it('forwards "invalidateOnBuild" calls to runAPI', async () => {
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnBuild: true,
    });

    assert(
      mockCast(mockRunApi.invalidateOnBuild).called,
      'Invalidate was called',
    );
  });

  it('forwards "invalidateOnConfigKeyChange" calls to runAPI', async () => {
    await fs.mkdirp('/project_root');
    await fs.writeFile(
      '/project_root/config.json',
      JSON.stringify({key1: 'value1'}),
    );
    sinon.spy(fs, 'readFile');
    sinon.spy(fs, 'readFileSync');
    const mockRunApi = getMockRunApi();
    await runConfigRequest(mockRunApi, {
      ...baseRequest,
      invalidateOnConfigKeyChange: [
        {
          configKey: 'key1',
          filePath: toProjectPath(
            projectRoot,
            path.join('project_root', 'config.json'),
          ),
        },
      ],
    });

    const fsCall = mockCast(fs).readFile.getCall(0);
    assert.deepEqual(
      fsCall?.args,
      [path.join('project_root', 'config.json'), 'utf8'],
      'readFile was called',
    );

    const call = mockCast(mockRunApi.invalidateOnConfigKeyChange).getCall(0);
    assert.deepEqual(
      call.args,
      ['config.json', 'key1', hashString('"value1"')],
      'Invalidate was called for key1',
    );
  });
});