mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
6b40f35032
Summary: This diff adds snapshot `diff-api-snapshot` script for public JS API breaking change detection. ### Motivation Detecting if there are any breaking changes introduced in the commit. It is achieved by comparing `ReactNativeApi.d.ts` rollup from the current and previous revision. This is a naive implementation with a three possible outcomes: - BREAKING - POTENTIALLY_NOT_BREAKING, - NOT_BREAKING The algorithm analyses exported top-level statements (after inlining) in both rollups and tries to create a mapping between them by name. The **BREAKING** outcome happens whenever the statement is: - removed - renamed - changed - not exported anymore (private) The **POTENTIALLY_NOT_BREAKING** outcome happens if it's not BREAKING and the new statement is added. The **NOT_BREAKING** outcome happens if public API snapshot doesn't change. Changelog: [General][Added] - Add public JS API breaking change detection under `yarn diff-api-snapshot` script. Pull Request resolved: https://github.com/facebook/react-native/pull/51972 Test Plan: Signals, added tests. In `react-native-github` run: `yarn test scripts/diff-api-snapshot/__tests__/diffApiSnapshot-test.js` Rollback Plan: Reviewed By: j-piasecki Differential Revision: D76430965 Pulled By: coado fbshipit-source-id: 095a196aa4f643501db0af9262556ddefff5d30d
180 lines
5.5 KiB
JavaScript
180 lines
5.5 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @format
|
|
* @oncall react_native
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const {Result, diffApiSnapshot} = require('../diffApiSnapshot');
|
|
|
|
describe('diffApiSnapshot', () => {
|
|
test('should detect breaking change when a statement is deleted', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
export declare const DeletedExport: string;
|
|
`;
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.BREAKING);
|
|
expect(res.changedApis).toEqual(['DeletedExport']);
|
|
});
|
|
|
|
test('should detect breaking change when a statement is changed', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
`;
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_3; // Changed from AccessibilityInfo_2 to AccessibilityInfo_3
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.BREAKING);
|
|
expect(res.changedApis).toEqual(['AccessibilityInfo']);
|
|
});
|
|
|
|
test('should detect potentially not breaking change when a statement is added', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
`;
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
export declare const NewExport: string; // New export added
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.POTENTIALLY_NON_BREAKING);
|
|
expect(res.changedApis).toEqual(['NewExport']);
|
|
});
|
|
|
|
test('should detect not breaking change when nothing is changed', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityActionEvent = NativeSyntheticEvent<
|
|
Readonly<{
|
|
actionName: string;
|
|
}>
|
|
>;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, prevSnapshot);
|
|
expect(res.result).toBe(Result.NON_BREAKING);
|
|
expect(res.changedApis).toEqual([]);
|
|
});
|
|
|
|
test('should handle complex type declarations', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type ComplexType = {
|
|
prop1: string;
|
|
prop2: number;
|
|
prop3: {
|
|
nestedProp1: boolean;
|
|
nestedProp2: Array<string>;
|
|
};
|
|
};
|
|
`;
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type ComplexType = {
|
|
prop1: string;
|
|
prop2: number;
|
|
prop3: {
|
|
nestedProp1: boolean;
|
|
nestedProp2: Array<string>;
|
|
nestedProp3: number; // Added property
|
|
};
|
|
};
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.BREAKING);
|
|
expect(res.changedApis).toEqual(['ComplexType']);
|
|
});
|
|
|
|
test('should handle interface declarations', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export interface TestInterface {
|
|
method1(): void;
|
|
property1: string;
|
|
}
|
|
`;
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export interface TestInterface {
|
|
method1(): void;
|
|
property1: string;
|
|
method2(): number; // Added method
|
|
}
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.BREAKING);
|
|
expect(res.changedApis).toEqual(['TestInterface']);
|
|
});
|
|
|
|
test('should handle const and type of the same name', () => {
|
|
const prevSnapshot = `
|
|
import * as React from 'react';
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
export declare type AccessibilityInfo = typeof AccessibilityInfo;
|
|
`;
|
|
|
|
const newSnapshot = `
|
|
import * as React from 'react';
|
|
export declare type AccessibilityInfo = typeof AccessibilityInfo;
|
|
export declare const AccessibilityInfo: typeof AccessibilityInfo_2;
|
|
`;
|
|
|
|
const res = diffApiSnapshot(prevSnapshot, newSnapshot);
|
|
expect(res.result).toBe(Result.NON_BREAKING);
|
|
expect(res.changedApis).toEqual([]);
|
|
});
|
|
});
|