Add spread tests
This commit is contained in:
parent
bf866cea7b
commit
0fea59f642
|
@ -3,8 +3,5 @@ var {h?} = { h?: 1 };
|
|||
var {i}: string | number = { i: 2 };
|
||||
var {i1}: string | number| {} = { i1: 2 };
|
||||
var { f2: {f21} = { f212: "string" } }: any = undefined;
|
||||
var { ...d1 } = {
|
||||
a: 1, b: 1, d1: 9, e: 10
|
||||
}
|
||||
var {1} = { 1 };
|
||||
var {"prop"} = { "prop": 1 };
|
||||
var {"prop"} = { "prop": 1 };
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
interface Congealed<T, U> {
|
||||
...T
|
||||
...U
|
||||
}
|
||||
|
||||
let sandwich: Congealed<{jam: number }, { peanutButter: number }>;
|
||||
sandwich.jam;
|
||||
sandwich.peanutButter;
|
96
tests/cases/conformance/types/spread/objectSpread.ts
Normal file
96
tests/cases/conformance/types/spread/objectSpread.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
// @target: es5
|
||||
let o = { a: 1, b: 'no' }
|
||||
let o2 = { b: 'yes', c: true }
|
||||
let swap = { a: 'yes', b: -1 };
|
||||
|
||||
let addAfter: { a: number, b: string, c: boolean } =
|
||||
{ ...o, c: false }
|
||||
let addBefore: { a: number, b: string, c: boolean } =
|
||||
{ c: false, ...o }
|
||||
// Note: ignore still changes the order that properties are printed
|
||||
let ignore: { a: number, b: string } =
|
||||
{ b: 'ignored', ...o }
|
||||
let override: { a: number, b: string } =
|
||||
{ ...o, b: 'override' }
|
||||
let nested: { a: number, b: boolean, c: string } =
|
||||
{ ...{ a: 3, ...{ b: false, c: 'overriden' } }, c: 'whatever' }
|
||||
let combined: { a: number, b: string, c: boolean } =
|
||||
{ ...o, ...o2 }
|
||||
let combinedBefore: { a: number, b: string, c: boolean } =
|
||||
{ b: 'ok', ...o, ...o2 }
|
||||
let combinedMid: { a: number, b: string, c: boolean } =
|
||||
{ ...o, b: 'ok', ...o2 }
|
||||
let combinedAfter: { a: number, b: string, c: boolean } =
|
||||
{ ...o, ...o2, b: 'ok' }
|
||||
let combinedNested: { a: number, b: boolean, c: string, d: string } =
|
||||
{ ...{ a: 4, ...{ b: false, c: 'overriden' } }, d: 'actually new', ...{ a: 5, d: 'maybe new' } }
|
||||
let combinedNestedChangeType: { a: number, b: boolean, c: number } =
|
||||
{ ...{ a: 1, ...{ b: false, c: 'overriden' } }, c: -1 }
|
||||
let propertyNested: { a: { a: number, b: string } } =
|
||||
{ a: { ... o } }
|
||||
// accessors don't copy the descriptor
|
||||
// (which means that readonly getters become read/write properties)
|
||||
let op = { get a () { return 6 } };
|
||||
let getter: { a: number, c: number } =
|
||||
{ ...op, c: 7 }
|
||||
getter.a = 12;
|
||||
|
||||
// null and undefined are just skipped
|
||||
let spreadNull: { a: number } =
|
||||
{ a: 7, ...null }
|
||||
let spreadUndefined: { a: number } =
|
||||
{ a: 7, ...undefined }
|
||||
|
||||
// methods are not enumerable
|
||||
class C { p = 1; m() { } }
|
||||
let c: C = new C()
|
||||
let spreadC: { p: number } = { ...c }
|
||||
|
||||
// own methods are enumerable
|
||||
let cplus: { p: number, plus(): void } = { ...c, plus() { return this.p + 1; } };
|
||||
cplus.plus();
|
||||
|
||||
// new field's type conflicting with existing field is OK
|
||||
let changeTypeAfter: { a: string, b: string } =
|
||||
{ ...o, a: 'wrong type?' }
|
||||
let changeTypeBefore: { a: number, b: string } =
|
||||
{ a: 'wrong type?', ...o };
|
||||
let changeTypeBoth: { a: string, b: number } =
|
||||
{ ...o, ...swap };
|
||||
|
||||
// optional
|
||||
let definiteBoolean: { sn: boolean };
|
||||
let definiteString: { sn: string };
|
||||
let optionalString: { sn?: string };
|
||||
let optionalNumber: { sn?: number };
|
||||
let optionalUnionStops: { sn: string | number | boolean } = { ...definiteBoolean, ...definiteString, ...optionalNumber };
|
||||
let optionalUnionDuplicates: { sn: string | number } = { ...definiteBoolean, ...definiteString, ...optionalString, ...optionalNumber };
|
||||
let allOptional: { sn?: string | number } = { ...optionalString, ...optionalNumber };
|
||||
|
||||
// computed property
|
||||
let computedFirst: { a: number, b: string, "before everything": number } =
|
||||
{ ['before everything']: 12, ...o, b: 'yes' }
|
||||
let computedMiddle: { a: number, b: string, c: boolean, "in the middle": number } =
|
||||
{ ...o, ['in the middle']: 13, b: 'maybe?', ...o2 }
|
||||
let computedAfter: { a: number, b: string, "at the end": number } =
|
||||
{ ...o, b: 'yeah', ['at the end']: 14 }
|
||||
// shortcut syntax
|
||||
let a = 12;
|
||||
let shortCutted: { a: number, b: string } = { ...o, a }
|
||||
|
||||
// generics
|
||||
function f<T, U>(t: T, u: U): { id: string, ...T, ...U } {
|
||||
return { id: 'id', ...t, ...u };
|
||||
}
|
||||
let exclusive: { id: string, a: number, b: string, c: string, d: boolean } =
|
||||
f({ a: 1, b: 'yes' }, { c: 'no', d: false })
|
||||
let overlap: { id: string, a: number, b: string } =
|
||||
f({ a: 1 }, { a: 2, b: 'extra' })
|
||||
let overlapConflict: { id:string, a: string } =
|
||||
f({ a: 1 }, { a: 'mismatch' })
|
||||
let overwriteId: { id: boolean, a: number, c: number, d: string } =
|
||||
f({ a: 1, id: true }, { c: 1, d: 'no' })
|
||||
|
||||
class D { m() { }; q = 2; }
|
||||
let classesAreWrong: { id: string, ...C, ...D } =
|
||||
f(new C(), new D())
|
40
tests/cases/conformance/types/spread/objectSpreadGeneric.ts
Normal file
40
tests/cases/conformance/types/spread/objectSpreadGeneric.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
function f<T, U, V>(t: T, u: U, v: V): void {
|
||||
let o: { ...T, ...U, ...V };
|
||||
const same: { ...T, ...U, ...V } = o; // ok
|
||||
const reversed: { ...V, ...U, ...T } = o; // error, reversed
|
||||
const reversed2: { ...U, ...T, ...V } = o; // error, U and T are still reversed
|
||||
const missingT: { ...U, ...V } = o; // error, missing T
|
||||
const missingU: { ...T, ...V } = o; // error, missing U
|
||||
const missingV: { ...T, ...U } = o; // error, missing V
|
||||
const atEnd: { ...T, ...U, second: string } = { ...t, ...u, second: 'foo' }; // ok
|
||||
const atBeginning: { first: string, ...T, ...U, } = { first: 'foo', ...t, ...u }; // ok
|
||||
|
||||
const emptyTarget: { } = { ...t, ...u } // ok
|
||||
const emptySource: { ...T, ...U } = { }; // error, {} is not assignable to U (or T)
|
||||
|
||||
let optionalNumber: { sn?: number };
|
||||
let optionalString: { sn?: string };
|
||||
let optionalBoolean: { sn?: boolean };
|
||||
const unionCutoff: { ...T, sn?: number | string | boolean } =
|
||||
{ ...optionalBoolean, ...t, ...optionalString, ...optionalNumber } // ok
|
||||
unionCutoff.sn; // ok
|
||||
const optionalCutoff = { ...t, ...optionalNumber }; // ok
|
||||
optionalCutoff.sn; // ok
|
||||
|
||||
const interspersed: { first: string, ...T, second: string, ...U, third: string } =
|
||||
{ first: '1', ...t, second: '2', ...u, third: '3' }; // ok
|
||||
const interspersedMissingU: { first: string, second: string, ...T, third: string } =
|
||||
{ first: '1', ...t, second: '2', ...u, third: '3' }; // error, 'U' is missing
|
||||
const interspersedOrder1: { first: string, ...T, second: string, ...U, third: string, secondsecond: string } =
|
||||
{ first: '1', ...t, second: '2', ...u, third: '3', secondsecond: 'false' }; // ok
|
||||
const interspersedOrder2: { first: string, second: string, secondsecond: string, third: string, ...T, ...U } =
|
||||
{ first: '1', ...t, second: '2', ...u, third: '3', secondsecond: 'false' }; // ok
|
||||
|
||||
|
||||
const mismatchFirst: { first: string, ...T, second: string, ...U, third: string } =
|
||||
{ firrrrrrst: '1', ...t, second: '2', ...u, third: '3' }; // error, 'first' not found
|
||||
const mismatchSecond: { first: string, ...T, second: string, ...U, third: string } =
|
||||
{ first: '1', ...t, ssssssssecond: '2', ...u, third: '3' }; // error, 'second' not found
|
||||
const mismatchLast: { first: string, ...T, second: string, ...U, third: string } =
|
||||
{ first: '1', ...t, second: '2', ...u, thirrrrrrrd: '3' }; // error, 'third' not found
|
||||
}
|
49
tests/cases/conformance/types/spread/objectSpreadNegative.ts
Normal file
49
tests/cases/conformance/types/spread/objectSpreadNegative.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
// @target: es5
|
||||
let o = { a: 1, b: 'no' }
|
||||
|
||||
/// private propagates
|
||||
class PrivateOptionalX {
|
||||
private x?: number;
|
||||
}
|
||||
class PublicX {
|
||||
public x: number;
|
||||
}
|
||||
let privateOptionalx: PrivateOptionalX;
|
||||
let publicx: PublicX;
|
||||
let o3 = { ...publicx, ...privateOptionalx };
|
||||
let sn: string | number = o3.x; // error, x is private
|
||||
let optionalString: { sn?: string };
|
||||
let optionalNumber: { sn?: number };
|
||||
let allOptional: { sn: string | number } = { ...optionalString, ...optionalNumber };
|
||||
// error, 'sn' is optional in source, required in target
|
||||
|
||||
// assignability as target
|
||||
interface Bool { b: boolean };
|
||||
interface Str { s: string };
|
||||
let spread: { ...Bool, ...Str } = { s: 'foo' }; // error, missing 'b'
|
||||
let b: Bool;
|
||||
spread = b; // error, missing 's'
|
||||
|
||||
// expressions are not allowed
|
||||
let o1 = { ...1 + 1 };
|
||||
let o2 = { ...(1 + 1) };
|
||||
|
||||
// literal repeats are not allowed, but spread repeats are fine
|
||||
let duplicated = { b: 'bad', ...o, b: 'bad', ...o2, b: 'bad' }
|
||||
let duplicatedSpread = { ...o, ...o }
|
||||
|
||||
// write-only properties get skipped
|
||||
let setterOnly = { ...{ set b (bad: number) { } } };
|
||||
setterOnly.b = 12; // error, 'b' does not exist
|
||||
|
||||
// methods are skipped because they aren't enumerable
|
||||
class C { p = 1; m() { } }
|
||||
let c: C = new C()
|
||||
let spreadC = { ...c }
|
||||
spreadC.m(); // error 'm' is not in '{ ... c }'
|
||||
|
||||
let callableConstructableSpread: { ...PublicX, (n: number): number, new (p: number) };
|
||||
callableConstructableSpread(12); // error, no call signature
|
||||
new callableConstructableSpread(12); // error, no construct signature
|
||||
|
||||
let callableSpread = { ...publicx, ...(n => n + 1) }; // error, can't spread functions
|
|
@ -0,0 +1,4 @@
|
|||
let o7 = { ...o? };
|
||||
let o8 = { ...*o };
|
||||
let o9 = { ...matchMedia() { }};
|
||||
let o10 = { ...get x() { return 12; }};
|
35
tests/cases/fourslash/completionListForObjectSpread.ts
Normal file
35
tests/cases/fourslash/completionListForObjectSpread.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
////let o = { a: 1, b: 'no' }
|
||||
////let o2 = { b: 'yes', c: true }
|
||||
////let swap = { a: 'yes', b: -1 };
|
||||
////let addAfter: { a: number, b: string, c: boolean } =
|
||||
//// { ...o, c: false }
|
||||
////let addBefore: { a: number, b: string, c: boolean } =
|
||||
//// { c: false, ...o }
|
||||
////let ignore: { a: number, b: string } =
|
||||
//// { b: 'ignored', ...o }
|
||||
////ignore./*1*/a;
|
||||
////let combinedNestedChangeType: { a: number, b: boolean, c: number } =
|
||||
//// { ...{ a: 1, ...{ b: false, c: 'overriden' } }, c: -1 }
|
||||
////combinedNestedChangeType./*2*/a;
|
||||
////let spreadNull: { a: number } =
|
||||
//// { a: 7, ...null }
|
||||
////let spreadUndefined: { a: number } =
|
||||
//// { a: 7, ...undefined }
|
||||
////spreadNull./*3*/a;
|
||||
////spreadUndefined./*4*/a;
|
||||
goTo.marker('1');
|
||||
verify.memberListContains('a', '(property) a: number');
|
||||
verify.memberListContains('b', '(property) b: string');
|
||||
verify.memberListCount(2);
|
||||
goTo.marker('2');
|
||||
verify.memberListContains('a', '(property) a: number');
|
||||
verify.memberListContains('b', '(property) b: boolean');
|
||||
verify.memberListContains('c', '(property) c: number');
|
||||
verify.memberListCount(3);
|
||||
goTo.marker('3');
|
||||
verify.memberListContains('a', '(property) a: number');
|
||||
verify.memberListCount(1);
|
||||
goTo.marker('4');
|
||||
verify.memberListContains('a', '(property) a: number');
|
||||
verify.memberListCount(1);
|
12
tests/cases/fourslash/findAllRefsForObjectSpread.ts
Normal file
12
tests/cases/fourslash/findAllRefsForObjectSpread.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////interface A1 { [|a|]: number };
|
||||
////interface A2 { [|a|]?: number };
|
||||
////let a12: { ...A1, ...A2 };
|
||||
////a12.[|a|];
|
||||
const ranges = test.ranges();
|
||||
// members of spread types only refer to themselves and the resulting property
|
||||
verify.referencesOf(ranges[0], [ranges[0], ranges[2]]);
|
||||
verify.referencesOf(ranges[1], [ranges[1], ranges[2]]);
|
||||
// but the resulting property refers to everything
|
||||
verify.referencesOf(ranges[2], ranges);
|
7
tests/cases/fourslash/goToDefinitionObjectSpread.ts
Normal file
7
tests/cases/fourslash/goToDefinitionObjectSpread.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////interface A1 { /*1*/a: number };
|
||||
////interface A2 { /*2*/a?: number };
|
||||
////let a12: { ...A1, ...A2 };
|
||||
////a12.a/*3*/;
|
||||
verify.goToDefinition('3', [ '1', '2' ]);
|
18
tests/cases/fourslash/renameObjectSpread.ts
Normal file
18
tests/cases/fourslash/renameObjectSpread.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
////interface A1 { [|a|]: number };
|
||||
////interface A2 { [|a|]?: number };
|
||||
////let a12: { ...A1, ...A2 };
|
||||
////a12.[|a|];
|
||||
const ranges = test.ranges();
|
||||
verify.assertHasRanges(ranges);
|
||||
|
||||
// A1 unions with A2, so rename A1.a and a12.a
|
||||
goTo.position(ranges[0].start);
|
||||
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[2]]);
|
||||
// A1 unions with A2, so rename A2.a and a12.a
|
||||
goTo.position(ranges[1].start);
|
||||
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[1], ranges[2]]);
|
||||
// a12.a unions A1.a and A2.a, so rename A1.a, A2.a and a12.a
|
||||
goTo.position(ranges[2].start);
|
||||
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[1], ranges[2]]);
|
Loading…
Reference in a new issue