jsii-diff
Version:
Assembly comparison for jsii
560 lines (539 loc) • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
// ----------------------------------------------------------------------
test('okay to add a new class', () => (0, util_1.expectNoError)(`
export class Foo1 { }
`, `
export class Foo1 { }
export class Foo2 { }
`));
// ----------------------------------------------------------------------
test('okay to add a new function to a class', () => (0, util_1.expectNoError)(`
export class Foo { }
`, `
export class Foo {
public bar(): void { }
}
`));
// ----------------------------------------------------------------------
test('not okay to add a required argument to a method', () => (0, util_1.expectError)(/newly required argument/, `
export class Foo {
public bar(arg1: string): void {
Array.isArray(arg1);
}
}
`, `
export class Foo {
public bar(arg1: string, arg2: string): void {
Array.isArray(arg1);
Array.isArray(arg2);
}
}
`));
// ----------------------------------------------------------------------
test('okay to make a required argument optional', () => (0, util_1.expectNoError)(`
export class Foo {
public bar(arg1: string, arg2: string): void {
Array.isArray(arg1);
Array.isArray(arg2);
}
}
`, `
export class Foo {
public bar(arg1: string, arg2?: string): void {
Array.isArray(arg1);
Array.isArray(arg2);
}
}
`));
// ----------------------------------------------------------------------
test('okay to turn required arguments into varargs', () => (0, util_1.expectNoError)(`
export class Foo {
public bar(arg1: string, arg2: number, arg3: number): void {
Array.isArray(arg1);
Array.isArray(arg2);
Array.isArray(arg3);
}
}
`, `
export class Foo {
public bar(arg1: string, ...args: number[]): void {
Array.isArray(arg1);
Array.isArray(args);
}
}
`));
// ----------------------------------------------------------------------
test('not allowed to change argument type to a different scalar', () => (0, util_1.expectError)(/method.*foo.*argument arg1, takes number \(formerly string\): string is not assignable to number/i, `
export class Foo {
public bar(arg1: string): void {
Array.isArray(arg1);
}
}
`, `
export class Foo {
public bar(arg1: number): void {
Array.isArray(arg1);
}
}
`));
// ----------------------------------------------------------------------
test('cannot add any abstract members to a subclassable class', () => (0, util_1.expectError)(/adds requirement for subclasses to implement 'piet'./, `
/**
* @subclassable
*/
export abstract class Henk {
abstract readonly name: string;
}
`, `
/**
* @subclassable
*/
export abstract class Henk {
abstract readonly name: string;
abstract readonly piet: string;
}
`));
// ----------------------------------------------------------------------
test('cannot add any members to a subclassable interface, not even optional ones', () => (0, util_1.expectError)(/adds requirement for subclasses to implement 'piet'./, `
/**
* @subclassable
*/
export interface IHenk {
name: string;
}
`, `
/**
* @subclassable
*/
export interface IHenk {
name: string;
piet?: string;
}
`));
// ----------------------------------------------------------------------
test('cannot make a member less visible', () => (0, util_1.expectError)(/changed from 'public' to 'protected'/, `
export class Henk {
public name: string = 'henk';
}
`, `
export class Henk {
protected name: string = 'henk';
}
`));
// ----------------------------------------------------------------------
describe('implement base types need to be present in updated type system', () => {
test('for interfaces', () => (0, util_1.expectError)(/not assignable to all base types anymore/, `
export interface IPapa {
readonly pipe: string;
}
export interface IBebe extends IPapa {
readonly pacifier: string;
}
`, `
export interface IPapa {
readonly pipe: string;
}
export interface IBebe {
readonly pacifier: string;
}
`));
test('for structs', () => (0, util_1.expectError)(/not assignable to all base types anymore/, `
export interface Papa {
readonly pipe: string;
}
export interface Bebe extends Papa {
readonly pacifier: string;
}
`, `
export interface Papa {
readonly pipe: string;
}
export interface Bebe {
readonly pacifier: string;
}
`));
test('for classes', () => (0, util_1.expectError)(/not assignable to all base types anymore/, `
export interface IPapa {
readonly pipe: string;
}
export class Bebe implements IPapa {
readonly pipe: string = 'pff';
readonly pacifier: string = 'mmm';
}
`, `
export interface IPapa {
readonly pipe: string;
}
export class Bebe {
readonly pipe: string = 'pff';
readonly pacifier: string = 'mmm';
}
`));
test('for base classes', () => (0, util_1.expectError)(/not assignable to all base types anymore/, `
export class Papa {
readonly pipe: string = 'pff';
}
export class Bebe extends Papa {
readonly pacifier: string = 'mmm';
}
`, `
export class Papa {
readonly pipe: string = 'pff';
}
export class Bebe {
readonly pacifier: string = 'mmm';
}
`));
test('new levels of inheritance are allowed', () => (0, util_1.expectNoError)(`
export class Papa {
readonly pipe: string = 'pff';
}
export class Bebe extends Papa {
readonly pacifier: string = 'mmm';
}
`, `
export class Papa {
readonly pipe: string = 'pff';
}
export class Inbetween extends Papa {
}
export class Bebe extends Inbetween {
readonly pacifier: string = 'mmm';
}
`));
test('change direct implementation to inherited implementation via interface', () => (0, util_1.expectNoError)(`
export interface IPapa {
readonly pipe: string;
}
export class Bebe implements IPapa {
readonly pipe: string = 'pff';
readonly pacifier: string = 'mmm';
}
`, `
export interface IPapa {
readonly pipe: string;
}
export interface IInbetween extends IPapa {
}
export class Bebe implements IInbetween {
readonly pipe: string = 'pff';
readonly pacifier: string = 'mmm';
}
`));
test('change direct implementation to inherited implementation via base class', () => (0, util_1.expectNoError)(`
export interface IPapa {
readonly pipe: string;
}
export class Bebe implements IPapa {
readonly pipe: string = 'pff';
readonly pacifier: string = 'mmm';
}
`, `
export interface IPapa {
readonly pipe: string;
}
export class Inbetween implements IPapa {
readonly pipe: string = 'pff';
}
export class Bebe extends Inbetween {
readonly pacifier: string = 'mmm';
}
`));
});
// ----------------------------------------------------------------------
test.each([
{
oldDecl: 'name: string',
newDecl: 'name?: string',
error: /type Optional<string> \(formerly string\): output type is now optional/,
},
{
oldDecl: 'name?: string',
newDecl: 'name: string',
error: undefined, // Strengthening is okay
},
{
oldDecl: 'name: string',
newDecl: 'name: string | number',
error: /number \| string is not assignable to string/,
},
{
oldDecl: 'name: string | number',
newDecl: 'name: string',
error: undefined, // Strengthening is okay
},
])('change class property ', ({ oldDecl, newDecl, error }) => (0, util_1.expectError)(error, `
export class Henk {
public readonly ${oldDecl} = 'henk';
}
`, `
export class Henk {
public readonly ${newDecl} = 'henk';
}
`));
// ----------------------------------------------------------------------
test.each([
{
oldDecl: 'name: string',
newDecl: 'name?: string',
error: /changed to Optional<string> \(formerly string\)/,
},
{
oldDecl: 'name?: string',
newDecl: 'name: string',
error: /changed to string \(formerly Optional<string>\)/,
},
{
oldDecl: 'name: string',
newDecl: 'name: string | number',
error: /changed to number \| string \(formerly string\)/,
},
{
oldDecl: 'name: string | number',
newDecl: 'name: string',
error: /changed to string \(formerly number \| string\)/,
},
])('cannot change a mutable class property type: %p to %p', ({ oldDecl, newDecl, error }) => (0, util_1.expectError)(error, `
export class Henk {
public ${oldDecl} = 'henk';
}
`, `
export class Henk {
public ${newDecl} = 'henk';
}
`));
// ----------------------------------------------------------------------
test('consider inherited constructor', () => (0, util_1.expectError)(/number is not assignable to string/, `
export class Super {
constructor(param: number) {
Array.isArray(param);
}
}
export class Sub extends Super {
}
`, `
export class Super {
constructor(param: number) {
Array.isArray(param);
}
}
export class Sub extends Super {
constructor(param: string) {
super(5);
Array.isArray(param);
}
}
`));
// ----------------------------------------------------------------------
test('consider inherited constructor, the other way', () => (0, util_1.expectError)(/newly required argument/, `
export class Super {
constructor(param: number) {
Array.isArray(param);
}
}
export class Sub extends Super {
constructor() {
super(5);
}
}
`, `
export class Super {
constructor(param: number) {
Array.isArray(param);
}
}
export class Sub extends Super {
}
`));
// ----------------------------------------------------------------------
test('method can be moved to supertype', () => (0, util_1.expectNoError)(`
export class Super {
}
export class Sub extends Super {
public foo(param: number) {
Array.isArray(param);
}
}
`, `
export class Super {
public foo(param: number) {
Array.isArray(param);
}
}
export class Sub extends Super {
}
`));
// ----------------------------------------------------------------------
test('property can be moved to supertype', () => (0, util_1.expectNoError)(`
export class Super {
}
export class Sub extends Super {
public foo: string = 'foo';
}
`, `
export class Super {
public foo: string = 'foo';
}
export class Sub extends Super {
}
`));
// ----------------------------------------------------------------------
test('subclassable is forever', () => (0, util_1.expectError)(/has gone from to non-/, `
/**
* @subclassable
*/
export class Super {
}
`, `
export class Super {
}
`));
// ----------------------------------------------------------------------
test('change from method to property', () => (0, util_1.expectError)(/changed from method to property/, `
export class Boom {
public foo() { return 12; }
}
`, `
export class Boom {
public get foo() { return 12; }
}
`));
// ----------------------------------------------------------------------
test('change from method with arguments to property', () => (0, util_1.expectError)(/changed from method to property/, `
export class Boom {
public foo(arg: number) { return 12 * arg; }
}
`, `
export class Boom {
public get foo() { return 12; }
}
`));
// ----------------------------------------------------------------------
test('change from property to method', () => (0, util_1.expectError)(/changed from property to method/, `
export class Boom {
public get foo() { return 12; }
}
`, `
export class Boom {
public foo() { return 12; }
}
`));
// ----------------------------------------------------------------------
test.each([
{
oldDecl: 'foo(arg: string) { Array.isArray(arg); }',
newDecl: 'foo(arg: string | number) { Array.isArray(arg); }',
},
{
oldDecl: 'foo(): string { return "x"; }',
newDecl: 'foo(): string | number { return "x"; }',
},
{
oldDecl: 'readonly foo: string = "x";',
newDecl: 'readonly foo: string | number = "x";',
},
])('cannot change any type in @subclassable class: %p to %p', ({ oldDecl, newDecl }) => (0, util_1.expectError)(/type is /, `
/** @subclassable */
export class Boom {
public ${oldDecl}
}
`, `
/** @subclassable */
export class Boom {
public ${newDecl}
}
`));
// ----------------------------------------------------------------------
test.each([
{
oldDecl: 'foo(arg: string): void;',
newDecl: 'foo(arg: string | number): void;',
},
{ oldDecl: 'foo(): string;', newDecl: 'foo(): string | number;' },
{
oldDecl: 'readonly foo: string;',
newDecl: 'readonly foo: string | number;',
},
])('cannot change any type in @subclassable interface: %p to %p', ({ oldDecl, newDecl }) => (0, util_1.expectError)(/type is /, `
/** @subclassable */
export interface IBoom {
${oldDecl}
}
`, `
/** @subclassable */
export interface IBoom {
${newDecl}
}
`));
// ----------------------------------------------------------------------
test.each([
// No usage => can add field
['', true],
// Return type => can add field
['foo(): TheStruct;', true],
['readonly foo: TheStruct;', true],
// Input type => can NOT add field
['foo: TheStruct;', false],
['foo(arg: TheStruct): void', false],
])('add required field to structs: refencing via %p -> allowed %p', (usageDecl, allowed) => (0, util_1.expectError)(allowed ? undefined : /newly required property 'fieldTwo' added/, `
export interface TheStruct {
readonly fieldOne: string;
}
export interface IConsumer {
${usageDecl}
}
`, `
export interface TheStruct {
readonly fieldOne: string;
readonly fieldTwo: string;
}
export interface IConsumer {
${usageDecl}
}
`));
// ----------------------------------------------------------------------
test.each([
// No usage => can add field
['', true],
// Return type => can NOT remove information
['foo(): TheStruct;', false],
['readonly foo: TheStruct;', false],
['foo: TheStruct;', false],
// Input type => can make optional
['foo(arg: TheStruct): void', true],
])('make required field optional: refencing via %p -> allowed %p', (usageDecl, allowed) => (0, util_1.expectError)(allowed ? undefined : /formerly required property 'fieldOne' is optional/, `
export interface TheStruct {
readonly fieldOne: string;
}
export interface IConsumer {
${usageDecl}
}
`, `
export interface TheStruct {
readonly fieldOne?: string;
}
export interface IConsumer {
${usageDecl}
}
`));
// ----------------------------------------------------------------------
test('will find mismatches in submodules', () => (0, util_1.expectError)(/number is not assignable to string/, {
'index.ts': 'export * as submodule from "./subdir"',
'subdir/index.ts': 'export class Foo { public static readonly PROP = "abc"; }',
}, {
'index.ts': 'export * as submodule from "./subdir"',
'subdir/index.ts': 'export class Foo { public static readonly PROP = 42; }',
}));
// ----------------------------------------------------------------------
test('allow remapping of FQNs', () => {
const original = `export class Foo1 { }`;
const updated = `export class Foo2 { }`;
// This should give no error because we remapped Foo1 to Foo2
const mms = (0, util_1.compare)(original, updated, {
fqnRemapping: { 'testpkg.Foo1': 'testpkg.Foo2' },
});
expect(Array.from(mms.messages())).toEqual([]);
});
//# sourceMappingURL=classes.test.js.map