Back to Repositories

Testing String Pluralization Utility in Apache Airflow

This test suite validates the pluralize utility function in Apache Airflow’s UI, which handles string pluralization with support for numbers and custom plural forms. The tests ensure proper handling of various cases including regular plurals, irregular plurals, and number formatting.

Test Coverage Overview

The test suite provides comprehensive coverage of the pluralize utility function with multiple test cases.

Key areas tested include:
  • Basic pluralization with numbers (0, 1, and large numbers)
  • Custom plural forms (e.g., goose/geese)
  • Cases where plural matches singular (e.g., Moose)
  • Number formatting with thousands separators
  • Optional count display

Implementation Analysis

The testing approach uses Jest’s describe/it pattern with TypeScript for type safety. The implementation leverages a data-driven testing pattern using a const array of test cases, each defining input parameters and expected output.

The test structure uses TypeScript’s satisfies operator to ensure type consistency across test cases.

Technical Details

Testing tools and configuration:
  • Testing Framework: Vitest
  • Language: TypeScript
  • Type Definition: PluralizeTestCase interface
  • Test Data Structure: Readonly array of test cases
  • Assertion Method: expect().toEqual()

Best Practices Demonstrated

The test suite demonstrates several testing best practices.

Notable practices include:
  • Type-safe test cases with TypeScript
  • Comprehensive edge case coverage
  • Data-driven test pattern
  • Clear test case organization
  • Consistent test structure

apache/airflow

airflow/ui/src/utils/pluralize.test.ts

            
/*!
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { describe, expect, it } from "vitest";

import { pluralize } from "./pluralize";

type PluralizeTestCase = {
  in: [string, number, string?, boolean?];
  out: string;
};

const pluralizeTestCases = [
  { in: ["DAG", 0, undefined, undefined], out: "0 DAGs" },
  { in: ["DAG", 1, undefined, undefined], out: "1 DAG" },
  { in: ["DAG", 12_000, undefined, undefined], out: "12,000 DAGs" },
  { in: ["DAG", 12_000_000, undefined, undefined], out: "12,000,000 DAGs" },
  { in: ["DAG", 0, undefined, undefined], out: "0 DAGs" },
  { in: ["DAG", 1, undefined, undefined], out: "1 DAG" },
  { in: ["DAG", 12_000, undefined, undefined], out: "12,000 DAGs" },
  { in: ["DAG", 12_000_000, undefined, undefined], out: "12,000,000 DAGs" },
  // Omit the count.
  { in: ["DAG", 0, undefined, true], out: "DAGs" },
  { in: ["DAG", 1, undefined, true], out: "DAG" },
  { in: ["DAG", 12_000, undefined, true], out: "DAGs" },
  { in: ["DAG", 12_000_000, undefined, true], out: "DAGs" },
  { in: ["DAG", 0, undefined, true], out: "DAGs" },
  { in: ["DAG", 1, undefined, true], out: "DAG" },
  { in: ["DAG", 12_000, undefined, true], out: "DAGs" },
  { in: ["DAG", 12_000_000, undefined, true], out: "DAGs" },
  // The casing of the string is preserved.
  { in: ["goose", 0, "geese", undefined], out: "0 geese" },
  { in: ["goose", 1, "geese", undefined], out: "1 goose" },
  // The plural form is different from the singular form.
  { in: ["Goose", 0, "Geese", undefined], out: "0 Geese" },
  { in: ["Goose", 1, "Geese", undefined], out: "1 Goose" },
  { in: ["Goose", 12_000, "Geese", undefined], out: "12,000 Geese" },
  { in: ["Goose", 12_000_000, "Geese", undefined], out: "12,000,000 Geese" },
  { in: ["Goose", 0, "Geese", undefined], out: "0 Geese" },
  { in: ["Goose", 1, "Geese", undefined], out: "1 Goose" },
  { in: ["Goose", 12_000, "Geese", undefined], out: "12,000 Geese" },
  { in: ["Goose", 12_000_000, "Geese", undefined], out: "12,000,000 Geese" },
  // In the case of "Moose", the plural is the same as the singular and you
  // probably wouldn't elect to use this function at all, but there could be
  // cases where dynamic data makes it unavoidable.
  { in: ["Moose", 0, "Moose", undefined], out: "0 Moose" },
  { in: ["Moose", 1, "Moose", undefined], out: "1 Moose" },
  { in: ["Moose", 12_000, "Moose", undefined], out: "12,000 Moose" },
  { in: ["Moose", 12_000_000, "Moose", undefined], out: "12,000,000 Moose" },
  { in: ["Moose", 0, "Moose", undefined], out: "0 Moose" },
  { in: ["Moose", 1, "Moose", undefined], out: "1 Moose" },
  { in: ["Moose", 12_000, "Moose", undefined], out: "12,000 Moose" },
  { in: ["Moose", 12_000_000, "Moose", undefined], out: "12,000,000 Moose" },
] as const satisfies Array<PluralizeTestCase>;

describe("pluralize", () => {
  it("case", () => {
    pluralizeTestCases.forEach((testCase) =>
      expect(
        pluralize(
          testCase.in[0],
          testCase.in[1],
          testCase.in[2],
          testCase.in[3],
        ),
      ).toEqual(testCase.out),
    );
  });
});