Back to Repositories

Testing PublicMutableBundleGraph Bundle Management in Parcel Bundler

This test suite validates the functionality of PublicMutableBundleGraph, a core component of Parcel’s bundling system. It focuses on bundle creation, public ID generation, and bundle group management through comprehensive unit tests.

Test Coverage Overview

The test suite provides thorough coverage of PublicMutableBundleGraph’s core functionality.

Key areas tested include:
  • Bundle public ID generation and uniqueness
  • Bundle group creation and management
  • Asset resolution and dependency handling
  • Multiple bundle addition safety checks

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern with mock asset graphs for isolated testing. The implementation leverages Flow type checking and follows a structured pattern of creating test fixtures through utility functions for assets, dependencies, and graph construction.

Framework-specific features include:
  • Nullthrows assertions for type safety
  • Mock asset graph generation
  • Dependency traversal testing

Technical Details

Testing tools and configuration:
  • Jest test framework
  • Flow strict-local type checking
  • Custom test utilities for creating mock assets and dependencies
  • InternalBundleGraph and MutableBundleGraph implementations
  • Asset graph simulation tools

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through comprehensive setup and validation.

Notable practices include:
  • Isolated test cases with clear assertions
  • Thorough mock data setup
  • Type-safe testing with Flow
  • Reusable test utilities
  • Clear test case descriptions

parcel-bundler/parcel

packages/core/core/test/PublicMutableBundleGraph.test.js

            
// @flow strict-local

import type {Dependency} from '@parcel/types';

import assert from 'assert';
import invariant from 'assert';
import InternalBundleGraph from '../src/BundleGraph';
import MutableBundleGraph from '../src/public/MutableBundleGraph';
import {DEFAULT_ENV, DEFAULT_TARGETS, DEFAULT_OPTIONS} from './test-utils';
import AssetGraph, {nodeFromAssetGroup} from '../src/AssetGraph';
import {createAsset as _createAsset} from '../src/assetUtils';
import {createDependency as _createDependency} from '../src/Dependency';
import nullthrows from 'nullthrows';
import {toProjectPath} from '../src/projectPath';

function createAsset(opts) {
  return _createAsset('/', opts);
}

function createDependency(opts) {
  return _createDependency('/', opts);
}

const id1 = '0123456789abcdef0123456789abcdef';
const id2 = '9876543210fedcba9876543210fedcba';

describe('PublicMutableBundleGraph', () => {
  it('creates publicIds for bundles', () => {
    let internalBundleGraph = InternalBundleGraph.fromAssetGraph(
      createMockAssetGraph(),
      false,
    );
    let mutableBundleGraph = new MutableBundleGraph(
      internalBundleGraph,
      DEFAULT_OPTIONS,
    );

    mutableBundleGraph.traverse(node => {
      if (
        node.type === 'dependency' &&
        mutableBundleGraph.getResolvedAsset(node.value)
      ) {
        let target = nullthrows(node.value.target);
        let group = mutableBundleGraph.createBundleGroup(node.value, target);
        let resolved = mutableBundleGraph.getResolvedAsset(node.value);
        if (resolved != null) {
          mutableBundleGraph.addBundleToBundleGroup(
            mutableBundleGraph.createBundle({
              entryAsset: resolved,
              target,
            }),
            group,
          );
        }
      }
    });

    assert.deepEqual(
      internalBundleGraph.getBundles().map(b => b.publicId),
      ['8LVYC', 'd7Pd5'],
    );
  });

  it('is safe to add a bundle to a bundleGroup multiple times', () => {
    let internalBundleGraph = InternalBundleGraph.fromAssetGraph(
      createMockAssetGraph(),
      false,
    );
    let mutableBundleGraph = new MutableBundleGraph(
      internalBundleGraph,
      DEFAULT_OPTIONS,
    );

    let dependency: Dependency;
    mutableBundleGraph.traverse((node, _, actions) => {
      if (node.type === 'dependency') {
        dependency = node.value;
        actions.stop();
      }
    });

    invariant(dependency != null);

    let target = nullthrows(dependency.target);
    let bundleGroup = mutableBundleGraph.createBundleGroup(dependency, target);
    let bundle = mutableBundleGraph.createBundle({
      entryAsset: nullthrows(mutableBundleGraph.getResolvedAsset(dependency)),
      target,
    });

    mutableBundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
    mutableBundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
  });
});

const stats = {size: 0, time: 0};
function createMockAssetGraph() {
  let graph = new AssetGraph();
  graph.setRootConnections({
    entries: [toProjectPath('/', '/index'), toProjectPath('/', '/index2')],
  });

  graph.resolveEntry(
    toProjectPath('/', '/index'),
    [
      {
        filePath: toProjectPath('/', '/path/to/index/src/main.js'),
        packagePath: toProjectPath('/', '/path/to/index'),
      },
    ],
    '1',
  );
  graph.resolveEntry(
    toProjectPath('/', '/index2'),
    [
      {
        filePath: toProjectPath('/', '/path/to/index/src/main2.js'),
        packagePath: toProjectPath('/', '/path/to/index'),
      },
    ],
    '2',
  );
  graph.resolveTargets(
    {
      filePath: toProjectPath('/', '/path/to/index/src/main.js'),
      packagePath: toProjectPath('/', '/path/to/index'),
    },
    DEFAULT_TARGETS,
    '3',
  );
  graph.resolveTargets(
    {
      filePath: toProjectPath('/', '/path/to/index/src/main2.js'),
      packagePath: toProjectPath('/', '/path/to/index'),
    },
    DEFAULT_TARGETS,
    '4',
  );

  let dep1 = createDependency({
    specifier: 'path/to/index/src/main.js',
    specifierType: 'esm',
    needsStableName: true,
    env: DEFAULT_ENV,
    target: DEFAULT_TARGETS[0],
  });
  let dep2 = createDependency({
    specifier: 'path/to/index/src/main2.js',
    specifierType: 'esm',
    needsStableName: true,
    env: DEFAULT_ENV,
    target: DEFAULT_TARGETS[0],
  });

  let filePath = toProjectPath('/', '/index.js');
  let req1 = {filePath, env: DEFAULT_ENV};
  graph.resolveDependency(dep1, nodeFromAssetGroup(req1).value, '5');
  graph.resolveAssetGroup(
    req1,
    [
      createAsset({
        id: id1,
        filePath,
        type: 'js',
        isSource: true,
        stats,
        env: DEFAULT_ENV,
      }),
    ],
    '6',
  );

  filePath = toProjectPath('/', '/index2.js');
  let req2 = {filePath, env: DEFAULT_ENV};
  graph.resolveDependency(dep2, nodeFromAssetGroup(req2).value, '7');
  graph.resolveAssetGroup(
    req2,
    [
      createAsset({
        id: id2,
        filePath,
        type: 'js',
        isSource: true,
        stats,
        env: DEFAULT_ENV,
      }),
    ],
    '8',
  );

  return graph;
}