diff --git a/test-runtime/interval.test.ts b/test-runtime/interval.test.ts index f16ff1a48c4..1ece7a2dd2a 100644 --- a/test-runtime/interval.test.ts +++ b/test-runtime/interval.test.ts @@ -1,6 +1,6 @@ import {TopLevelSpec} from '../src'; import {SelectionType} from '../src/selection'; -import {brush, embedFn, hits as hitsMaster, spec, testRenderFn, tuples} from './util'; +import {brush, embedFn, geoSpec, hits as hitsMaster, spec, testRenderFn, tuples} from './util'; import {Page} from 'puppeteer/lib/cjs/puppeteer/common/Page'; describe('interval selections at runtime in unit views', () => { @@ -197,4 +197,26 @@ describe('interval selections at runtime in unit views', () => { await testRender(`logpow_${i}`); } }); + + describe('geo-intervals', () => { + it('should add IDs to the store', async () => { + await embed(geoSpec()); + const store = await page.evaluate(brush('drag', 1)); + expect(store).toHaveLength(13); + for (const t of store) { + expect(t).toHaveProperty('_vgsid_'); + } + await testRender(`geo_1`); + }); + + it('should respect projections', async () => { + await embed(geoSpec({encodings: ['longitude']})); + const store = await page.evaluate(brush('drag', 0)); + expect(store).toHaveLength(20); + for (const t of store) { + expect(t).toHaveProperty('_vgsid_'); + } + await testRender(`geo_0`); + }); + }); }); diff --git a/test-runtime/resources/interval/translate/geo-0.svg b/test-runtime/resources/interval/translate/geo-0.svg new file mode 100644 index 00000000000..4dfad2e2813 --- /dev/null +++ b/test-runtime/resources/interval/translate/geo-0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/translate/geo-1.svg b/test-runtime/resources/interval/translate/geo-1.svg new file mode 100644 index 00000000000..f49911ea46d --- /dev/null +++ b/test-runtime/resources/interval/translate/geo-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/translate/geo-2.svg b/test-runtime/resources/interval/translate/geo-2.svg new file mode 100644 index 00000000000..232ee66ff79 --- /dev/null +++ b/test-runtime/resources/interval/translate/geo-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/unit/geo_0.svg b/test-runtime/resources/interval/unit/geo_0.svg new file mode 100644 index 00000000000..bce629d1275 --- /dev/null +++ b/test-runtime/resources/interval/unit/geo_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/unit/geo_1.svg b/test-runtime/resources/interval/unit/geo_1.svg new file mode 100644 index 00000000000..4dfad2e2813 --- /dev/null +++ b/test-runtime/resources/interval/unit/geo_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/zoom/geo-0.svg b/test-runtime/resources/interval/zoom/geo-0.svg new file mode 100644 index 00000000000..4dfad2e2813 --- /dev/null +++ b/test-runtime/resources/interval/zoom/geo-0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/zoom/geo-1.svg b/test-runtime/resources/interval/zoom/geo-1.svg new file mode 100644 index 00000000000..1b8107eeb1c --- /dev/null +++ b/test-runtime/resources/interval/zoom/geo-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/resources/interval/zoom/geo-2.svg b/test-runtime/resources/interval/zoom/geo-2.svg new file mode 100644 index 00000000000..253b2a67fc9 --- /dev/null +++ b/test-runtime/resources/interval/zoom/geo-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtime/translate.test.ts b/test-runtime/translate.test.ts index 2f944c8fd31..ba0c7f3cd9b 100644 --- a/test-runtime/translate.test.ts +++ b/test-runtime/translate.test.ts @@ -6,6 +6,7 @@ import { brush, compositeTypes, embedFn, + geoSpec, hits as hitsMaster, parentSelector, spec, @@ -16,117 +17,97 @@ import { import {Page} from 'puppeteer/lib/cjs/puppeteer/common/Page'; import {TopLevelSpec} from '../src'; -for (const bind of [bound, unbound]) { - describe(`Translate ${bind} interval selections at runtime`, () => { - let page: Page; - let embed: (specification: TopLevelSpec) => Promise; - let testRender: (filename: string) => Promise; - - beforeAll(async () => { - page = await (global as any).__BROWSER__.newPage(); - embed = embedFn(page); - testRender = testRenderFn(page, `interval/translate/${bind}`); - await page.goto('http://0.0.0.0:8000/test-runtime/'); - }); +describe('Translate interval selections at runtime', () => { + let page: Page; + let embed: (specification: TopLevelSpec) => Promise; + let testRender: (filename: string) => Promise; - afterAll(async () => { - await page.close(); - }); + beforeAll(async () => { + page = await (global as any).__BROWSER__.newPage(); + embed = embedFn(page); + await page.goto('http://0.0.0.0:8000/test-runtime/'); + }); - const type = 'interval'; - const hits = hitsMaster.interval; - const binding = bind === bound ? {bind: 'scales'} : {}; - - const assertExtent = { - [unbound]: { - x: ['isAbove', 'isBelow'], - y: ['isBelow', 'isAbove'] - }, - [bound]: { - x: ['isBelow', 'isAbove'], - y: ['isAbove', 'isBelow'] - } - }; - - it('should move back-and-forth', async () => { - for (let i = 0; i < hits.translate.length; i++) { - await embed(spec('unit', i, {type, ...binding})); - const drag = (await page.evaluate(brush('drag', i)))[0]; - await testRender(`${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; - assert[assertExtent[bind].x[i]](translate.values[0][0], drag.values[0][0]); - assert[assertExtent[bind].x[i]](translate.values[0][1], drag.values[0][1]); - assert[assertExtent[bind].y[i]](translate.values[1][0], drag.values[1][0]); - assert[assertExtent[bind].y[i]](translate.values[1][1], drag.values[1][1]); - await testRender(`${i}-1`); - } - }); + afterAll(async () => { + await page.close(); + }); - it('should work with binned domains', async () => { - for (let i = 0; i < hits.bins.length; i++) { - await embed( - spec( - 'unit', - 1, - {type, ...binding, encodings: ['y']}, - { - x: {aggregate: 'count', type: 'quantitative'}, - y: {bin: true}, - color: {value: 'steelblue', field: null, type: null} - } - ) - ); - const drag = (await page.evaluate(brush('bins', i)))[0]; - await testRender(`bins_${i}-0`); - const translate = (await page.evaluate(brush('bins_translate', i, null, bind === unbound)))[0]; - assert[assertExtent[bind].y[i]](translate.values[0][0], drag.values[0][0]); - assert[assertExtent[bind].y[i]](translate.values[0][1], drag.values[0][1]); - await testRender(`bins_${i}-1`); - } - }); + const hits = hitsMaster.interval; - it('should work with temporal domains', async () => { - // await jestPuppeteer.debug(); - const values = tuples.map(d => ({...d, a: new Date(2017, d.a)})); - const toNumber = (a: any) => a[0].values[0].map((d: any) => +d); - - for (let i = 0; i < hits.translate.length; i++) { - await embed(spec('unit', i, {type, ...binding, encodings: ['x']}, {values, x: {type: 'temporal'}})); - const drag = toNumber(await page.evaluate(brush('drag', i))); - await testRender(`temporal_${i}-0`); - const translate = toNumber(await page.evaluate(brush('translate', i, null, bind === unbound))); - assert[assertExtent[bind].x[i]](translate[0], drag[0]); - assert[assertExtent[bind].x[i]](translate[1], drag[1]); - await testRender(`temporal_${i}-1`); - } - }); + for (const bind of [bound, unbound]) { + describe(`${bind} intervals`, () => { + beforeAll(() => { + testRender = testRenderFn(page, `interval/translate/${bind}`); + }); - it('should work with log/pow scales', async () => { - for (let i = 0; i < hits.translate.length; i++) { - await embed( - spec( - 'unit', - i, - {type, ...binding}, - { - x: {scale: {type: 'pow', exponent: 1.5}}, - y: {scale: {type: 'log'}} - } - ) - ); - const drag = (await page.evaluate(brush('drag', i)))[0]; - await testRender(`logpow_${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; - assert[assertExtent[bind].x[i]](translate.values[0][0], drag.values[0][0]); - assert[assertExtent[bind].x[i]](translate.values[0][1], drag.values[0][1]); - assert[assertExtent[bind].y[i]](translate.values[1][0], drag.values[1][0]); - assert[assertExtent[bind].y[i]](translate.values[1][1], drag.values[1][1]); - await testRender(`logpow_${i}-1`); - } - }); + const type = 'interval'; + const binding = bind === bound ? {bind: 'scales'} : {}; - if (bind === unbound) { - it('should work with ordinal/nominal domains', async () => { + const assertExtent = { + [unbound]: { + x: ['isAbove', 'isBelow'], + y: ['isBelow', 'isAbove'] + }, + [bound]: { + x: ['isBelow', 'isAbove'], + y: ['isAbove', 'isBelow'] + } + }; + + it('should move back-and-forth', async () => { + for (let i = 0; i < hits.translate.length; i++) { + await embed(spec('unit', i, {type, ...binding})); + const drag = (await page.evaluate(brush('drag', i)))[0]; + await testRender(`${i}-0`); + const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; + assert[assertExtent[bind].x[i]](translate.values[0][0], drag.values[0][0]); + assert[assertExtent[bind].x[i]](translate.values[0][1], drag.values[0][1]); + assert[assertExtent[bind].y[i]](translate.values[1][0], drag.values[1][0]); + assert[assertExtent[bind].y[i]](translate.values[1][1], drag.values[1][1]); + await testRender(`${i}-1`); + } + }); + + it('should work with binned domains', async () => { + for (let i = 0; i < hits.bins.length; i++) { + await embed( + spec( + 'unit', + 1, + {type, ...binding, encodings: ['y']}, + { + x: {aggregate: 'count', type: 'quantitative'}, + y: {bin: true}, + color: {value: 'steelblue', field: null, type: null} + } + ) + ); + const drag = (await page.evaluate(brush('bins', i)))[0]; + await testRender(`bins_${i}-0`); + const translate = (await page.evaluate(brush('bins_translate', i, null, bind === unbound)))[0]; + assert[assertExtent[bind].y[i]](translate.values[0][0], drag.values[0][0]); + assert[assertExtent[bind].y[i]](translate.values[0][1], drag.values[0][1]); + await testRender(`bins_${i}-1`); + } + }); + + it('should work with temporal domains', async () => { + // await jestPuppeteer.debug(); + const values = tuples.map(d => ({...d, a: new Date(2017, d.a)})); + const toNumber = (a: any) => a[0].values[0].map((d: any) => +d); + + for (let i = 0; i < hits.translate.length; i++) { + await embed(spec('unit', i, {type, ...binding, encodings: ['x']}, {values, x: {type: 'temporal'}})); + const drag = toNumber(await page.evaluate(brush('drag', i))); + await testRender(`temporal_${i}-0`); + const translate = toNumber(await page.evaluate(brush('translate', i, null, bind === unbound))); + assert[assertExtent[bind].x[i]](translate[0], drag[0]); + assert[assertExtent[bind].x[i]](translate[1], drag[1]); + await testRender(`temporal_${i}-1`); + } + }); + + it('should work with log/pow scales', async () => { for (let i = 0; i < hits.translate.length; i++) { await embed( spec( @@ -134,48 +115,89 @@ for (const bind of [bound, unbound]) { i, {type, ...binding}, { - x: {type: 'ordinal'}, - y: {type: 'nominal'} + x: {scale: {type: 'pow', exponent: 1.5}}, + y: {scale: {type: 'log'}} } ) ); const drag = (await page.evaluate(brush('drag', i)))[0]; - await testRender(`ord_${i}-0`); - const translate = (await page.evaluate(brush('translate', i, null, true)))[0]; + await testRender(`logpow_${i}-0`); + const translate = (await page.evaluate(brush('translate', i, null, bind === unbound)))[0]; assert[assertExtent[bind].x[i]](translate.values[0][0], drag.values[0][0]); assert[assertExtent[bind].x[i]](translate.values[0][1], drag.values[0][1]); assert[assertExtent[bind].y[i]](translate.values[1][0], drag.values[1][0]); assert[assertExtent[bind].y[i]](translate.values[1][1], drag.values[1][1]); - await testRender(`ord_${i}-1`); + await testRender(`logpow_${i}-1`); } }); - } else { - for (const specType of compositeTypes) { - const assertExtents = { - repeat: { - x: ['isBelow', 'isBelow', 'isBelow'], - y: ['isAbove', 'isAbove', 'isAbove'] - }, - facet: { - x: ['isBelow', 'isBelow', 'isBelow'], - y: ['isBelow', 'isAbove', 'isBelow'] - } - }; - it(`should work with shared scales in ${specType} views`, async () => { - for (let i = 0; i < hits[specType].length; i++) { - await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); - const parent = parentSelector(specType, i); - const xscale = await page.evaluate('view._runtime.scales.x.value.domain()'); - const yscale = await page.evaluate('view._runtime.scales.y.value.domain()'); - const drag = (await page.evaluate(brush(specType, i, parent)))[0]; - assert[assertExtents[specType].x[i]](drag.values[0][0], xscale[0], `iter: ${i}`); - assert[assertExtents[specType].x[i]](drag.values[0][1], xscale[1], `iter: ${i}`); - assert[assertExtents[specType].y[i]](drag.values[1][0], yscale[0], `iter: ${i}`); - assert[assertExtents[specType].y[i]](drag.values[1][1], yscale[1], `iter: ${i}`); - await testRender(`${specType}_${i}`); + + if (bind === unbound) { + it('should work with ordinal/nominal domains', async () => { + for (let i = 0; i < hits.translate.length; i++) { + await embed( + spec( + 'unit', + i, + {type, ...binding}, + { + x: {type: 'ordinal'}, + y: {type: 'nominal'} + } + ) + ); + const drag = (await page.evaluate(brush('drag', i)))[0]; + await testRender(`ord_${i}-0`); + const translate = (await page.evaluate(brush('translate', i, null, true)))[0]; + assert[assertExtent[bind].x[i]](translate.values[0][0], drag.values[0][0]); + assert[assertExtent[bind].x[i]](translate.values[0][1], drag.values[0][1]); + assert[assertExtent[bind].y[i]](translate.values[1][0], drag.values[1][0]); + assert[assertExtent[bind].y[i]](translate.values[1][1], drag.values[1][1]); + await testRender(`ord_${i}-1`); } }); + } else { + for (const specType of compositeTypes) { + const assertExtents = { + repeat: { + x: ['isBelow', 'isBelow', 'isBelow'], + y: ['isAbove', 'isAbove', 'isAbove'] + }, + facet: { + x: ['isBelow', 'isBelow', 'isBelow'], + y: ['isBelow', 'isAbove', 'isBelow'] + } + }; + it(`should work with shared scales in ${specType} views`, async () => { + for (let i = 0; i < hits[specType].length; i++) { + await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); + const parent = parentSelector(specType, i); + const xscale = await page.evaluate('view._runtime.scales.x.value.domain()'); + const yscale = await page.evaluate('view._runtime.scales.y.value.domain()'); + const drag = (await page.evaluate(brush(specType, i, parent)))[0]; + assert[assertExtents[specType].x[i]](drag.values[0][0], xscale[0], `iter: ${i}`); + assert[assertExtents[specType].x[i]](drag.values[0][1], xscale[1], `iter: ${i}`); + assert[assertExtents[specType].y[i]](drag.values[1][0], yscale[0], `iter: ${i}`); + assert[assertExtents[specType].y[i]](drag.values[1][1], yscale[1], `iter: ${i}`); + await testRender(`${specType}_${i}`); + } + }); + } } + }); + } + + it('should work with geo intervals', async () => { + testRender = testRenderFn(page, `interval/translate`); + + await embed(geoSpec()); + const drag = await page.evaluate(brush('drag', 1)); + expect(drag).toHaveLength(13); + await testRender(`geo-0`); + + for (let i = 0; i < hits.translate.length; i++) { + const translate = await page.evaluate(brush('translate', i, null, true)); + expect(translate.length).toBeGreaterThan(0); + await testRender(`geo-${i + 1}`); } }); -} +}); diff --git a/test-runtime/util.ts b/test-runtime/util.ts index 192974f3a59..0928ddbd026 100644 --- a/test-runtime/util.ts +++ b/test-runtime/util.ts @@ -3,7 +3,7 @@ import {sync as mkdirp} from 'mkdirp'; import {Page} from 'puppeteer/lib/cjs/puppeteer/common/Page'; import {promisify} from 'util'; import {stringValue} from 'vega-util'; -import {SelectionResolution, SelectionType} from '../src/selection'; +import {IntervalSelectionConfigWithoutType, SelectionResolution, SelectionType} from '../src/selection'; import {NormalizedLayerSpec, NormalizedUnitSpec, TopLevelSpec} from '../src/spec'; const generate = process.env.VL_GENERATE_TESTS; @@ -189,6 +189,64 @@ export function spec(compose: ComposeType, iter: number, sel: any, opts: any = { } } +export function geoSpec(selDef?: IntervalSelectionConfigWithoutType): TopLevelSpec { + return { + width: 500, + height: 300, + projection: {type: 'albersUsa'}, + data: { + values: [ + {latitude: 31.95376472, longitude: -89.23450472}, + {latitude: 30.68586111, longitude: -95.01792778}, + {latitude: 38.94574889, longitude: -104.5698933}, + {latitude: 42.74134667, longitude: -78.05208056}, + {latitude: 30.6880125, longitude: -81.90594389}, + {latitude: 34.49166667, longitude: -88.20111111}, + {latitude: 32.85048667, longitude: -86.61145333}, + {latitude: 43.08751, longitude: -88.17786917}, + {latitude: 40.67331278, longitude: -80.64140639}, + {latitude: 40.44725889, longitude: -92.22696056}, + {latitude: 33.93011222, longitude: -89.34285194}, + {latitude: 46.88384889, longitude: -96.35089861}, + {latitude: 41.51961917, longitude: -87.40109333}, + {latitude: 31.42127556, longitude: -97.79696778}, + {latitude: 39.60416667, longitude: -116.0050597}, + {latitude: 32.46047167, longitude: -85.68003611}, + {latitude: 41.98934083, longitude: -88.10124278}, + {latitude: 48.88434111, longitude: -99.62087694}, + {latitude: 33.53456583, longitude: -89.31256917}, + {latitude: 41.43156583, longitude: -74.39191722}, + {latitude: 41.97602222, longitude: -114.6580911}, + {latitude: 41.30716667, longitude: -85.06433333}, + {latitude: 32.52883861, longitude: -94.97174556}, + {latitude: 42.57450861, longitude: -84.81143139}, + {latitude: 41.11668056, longitude: -98.05033639}, + {latitude: 32.52943944, longitude: -86.32822139}, + {latitude: 48.30079861, longitude: -102.4063514}, + {latitude: 40.65138528, longitude: -98.07978667}, + {latitude: 32.76124611, longitude: -89.53007139}, + {latitude: 32.11931306, longitude: -88.1274625} + ] + }, + mark: 'circle', + params: [ + { + name: 'sel', + select: {type: 'interval', ...selDef} + } + ], + encoding: { + longitude: {field: 'longitude', type: 'quantitative'}, + latitude: {field: 'latitude', type: 'quantitative'}, + color: { + condition: {param: 'sel', empty: false, value: 'goldenrod'}, + value: 'steelblue' + }, + size: {value: 10} + } + }; +} + export function unitNameRegex(specType: ComposeType, idx: number) { const name = UNIT_NAMES[specType][idx].replace('child_', ''); return new RegExp(`child(.*?)_${name}`); diff --git a/test-runtime/zoom.test.ts b/test-runtime/zoom.test.ts index 4ae6169ef44..f823ec3ba01 100644 --- a/test-runtime/zoom.test.ts +++ b/test-runtime/zoom.test.ts @@ -1,7 +1,18 @@ /* eslint-disable jest/expect-expect */ import {assert} from 'chai'; -import {bound, brush, compositeTypes, embedFn, parentSelector, spec, testRenderFn, tuples, unbound} from './util'; +import { + bound, + brush, + compositeTypes, + embedFn, + geoSpec, + parentSelector, + spec, + testRenderFn, + tuples, + unbound +} from './util'; const hits = { zoom: [9, 23], bins: [8, 2] @@ -16,140 +27,115 @@ function zoom(key: string, idx: number, direction: InOut, parent?: string, targe return `zoom(${hits[key][idx]}, ${delta}, ${parent}, ${targetBrush})`; } -const cmp = (a: number, b: number) => a - b; +describe('Zoom interval selections at runtime', () => { + let page: Page; + let embed: (specification: TopLevelSpec) => Promise; + let testRender: (filename: string) => Promise; -for (const bind of [bound, unbound]) { - describe(`Zoom ${bind} interval selections at runtime`, () => { - let page: Page; - let embed: (specification: TopLevelSpec) => Promise; - let testRender: (filename: string) => Promise; - - beforeAll(async () => { - page = await (global as any).__BROWSER__.newPage(); - embed = embedFn(page); - testRender = testRenderFn(page, `interval/zoom/${bind}`); - await page.goto('http://0.0.0.0:8000/test-runtime/'); - }); - - afterAll(async () => { - await page.close(); - }); + beforeAll(async () => { + page = await (global as any).__BROWSER__.newPage(); + embed = embedFn(page); + await page.goto('http://0.0.0.0:8000/test-runtime/'); + }); - const type = 'interval'; - const binding = bind === bound ? {bind: 'scales'} : {}; + afterAll(async () => { + await page.close(); + }); - const assertExtent = { - in: ['isAtLeast', 'isAtMost'], - out: ['isAtMost', 'isAtLeast'] - }; + for (const bind of [bound, unbound]) { + describe(`Zoom ${bind} interval selections at runtime`, () => { + beforeAll(() => { + testRender = testRenderFn(page, `interval/zoom/${bind}`); + }); - async function setup(brushKey: string, idx: number, encodings: string[], parent?: string) { - const inOut: InOut = idx % 2 ? 'out' : 'in'; - let xold: number[]; - let yold: number[]; + const type = 'interval'; + const binding = bind === bound ? {bind: 'scales'} : {}; + const cmp = (a: number, b: number) => a - b; + + const assertExtent = { + in: ['isAtLeast', 'isAtMost'], + out: ['isAtMost', 'isAtLeast'] + }; + + async function setup(brushKey: string, idx: number, encodings: string[], parent?: string) { + const inOut: InOut = idx % 2 ? 'out' : 'in'; + let xold: number[]; + let yold: number[]; + + if (bind === unbound) { + const drag = (await page.evaluate(brush(brushKey, idx, parent)))[0]; + xold = drag.values[0].sort(cmp); + yold = encodings.includes('y') ? drag.values[encodings.indexOf('x') + 1].sort(cmp) : null; + } else { + xold = JSON.parse((await page.evaluate('JSON.stringify(view._runtime.scales.x.value.domain())')) as string); + yold = (await page.evaluate('view._runtime.scales.y.value.domain()')) as number[]; + } - if (bind === unbound) { - const drag = (await page.evaluate(brush(brushKey, idx, parent)))[0]; - xold = drag.values[0].sort(cmp); - yold = encodings.includes('y') ? drag.values[encodings.indexOf('x') + 1].sort(cmp) : null; - } else { - xold = JSON.parse((await page.evaluate('JSON.stringify(view._runtime.scales.x.value.domain())')) as string); - yold = (await page.evaluate('view._runtime.scales.y.value.domain()')) as number[]; + return {inOut, xold, yold}; } - return {inOut, xold, yold}; - } + it('should zoom in and out', async () => { + for (let i = 0; i < hits.zoom.length; i++) { + await embed(spec('unit', i, {type, ...binding})); + const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); + await testRender(`${inOut}-0`); - it('should zoom in and out', async () => { - for (let i = 0; i < hits.zoom.length; i++) { - await embed(spec('unit', i, {type, ...binding})); - const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); - await testRender(`${inOut}-0`); - - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; - const xnew = zoomed.values[0].sort(cmp); - const ynew = zoomed.values[1].sort(cmp); - await testRender(`${inOut}-1`); - assert[assertExtent[inOut][0]](xnew[0], xold[0]); - assert[assertExtent[inOut][1]](xnew[1], xold[1]); - assert[assertExtent[inOut][0]](ynew[0], yold[0]); - assert[assertExtent[inOut][1]](ynew[1], yold[1]); - } - }); + const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const xnew = zoomed.values[0].sort(cmp); + const ynew = zoomed.values[1].sort(cmp); + await testRender(`${inOut}-1`); + assert[assertExtent[inOut][0]](xnew[0], xold[0]); + assert[assertExtent[inOut][1]](xnew[1], xold[1]); + assert[assertExtent[inOut][0]](ynew[0], yold[0]); + assert[assertExtent[inOut][1]](ynew[1], yold[1]); + } + }); - it('should work with binned domains', async () => { - for (let i = 0; i < hits.bins.length; i++) { - const encodings = ['y']; - await embed( - spec( - 'unit', - 1, - {type, ...binding, encodings}, - { - x: {aggregate: 'count', type: 'quantitative'}, - y: {bin: true}, - color: {value: 'steelblue', field: null, type: null} - } - ) - ); + it('should work with binned domains', async () => { + for (let i = 0; i < hits.bins.length; i++) { + const encodings = ['y']; + await embed( + spec( + 'unit', + 1, + {type, ...binding, encodings}, + { + x: {aggregate: 'count', type: 'quantitative'}, + y: {bin: true}, + color: {value: 'steelblue', field: null, type: null} + } + ) + ); - const {inOut, yold} = await setup('bins', i, encodings); - await testRender(`bins_${inOut}-0`); + const {inOut, yold} = await setup('bins', i, encodings); + await testRender(`bins_${inOut}-0`); - const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, bind === unbound)))[0]; - const ynew = zoomed.values[0].sort(cmp); - assert[assertExtent[inOut][0]](ynew[0], yold[0]); - assert[assertExtent[inOut][1]](ynew[1], yold[1]); - await testRender(`bins_${inOut}-1`); - } - }); - - it('should work with temporal domains', async () => { - const values = tuples.map(d => ({...d, a: new Date(2017, d.a)})); - const encodings = ['x']; + const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, bind === unbound)))[0]; + const ynew = zoomed.values[0].sort(cmp); + assert[assertExtent[inOut][0]](ynew[0], yold[0]); + assert[assertExtent[inOut][1]](ynew[1], yold[1]); + await testRender(`bins_${inOut}-1`); + } + }); - for (let i = 0; i < hits.zoom.length; i++) { - await embed(spec('unit', i, {type, ...binding, encodings}, {values, x: {type: 'temporal'}})); - const {inOut, xold} = await setup('drag', i, encodings); - await testRender(`temporal_${inOut}-0`); + it('should work with temporal domains', async () => { + const values = tuples.map(d => ({...d, a: new Date(2017, d.a)})); + const encodings = ['x']; - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; - const xnew = zoomed.values[0].sort(cmp); - assert[assertExtent[inOut][0]](+xnew[0], +new Date(xold[0])); - assert[assertExtent[inOut][1]](+xnew[1], +new Date(xold[1])); - await testRender(`temporal_${inOut}-1`); - } - }); + for (let i = 0; i < hits.zoom.length; i++) { + await embed(spec('unit', i, {type, ...binding, encodings}, {values, x: {type: 'temporal'}})); + const {inOut, xold} = await setup('drag', i, encodings); + await testRender(`temporal_${inOut}-0`); - it('should work with log/pow scales', async () => { - for (let i = 0; i < hits.zoom.length; i++) { - await embed( - spec( - 'unit', - i, - {type, ...binding}, - { - x: {scale: {type: 'pow', exponent: 1.5}}, - y: {scale: {type: 'log'}} - } - ) - ); - const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); - await testRender(`logpow_${inOut}-0`); - - const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; - const xnew = zoomed.values[0].sort(cmp); - const ynew = zoomed.values[1].sort(cmp); - assert[assertExtent[inOut][0]](xnew[0], xold[0]); - assert[assertExtent[inOut][1]](xnew[1], xold[1]); - assert[assertExtent[inOut][0]](ynew[0], yold[0]); - assert[assertExtent[inOut][1]](ynew[1], yold[1]); - await testRender(`logpow_${inOut}-1`); - } - }); + const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; + const xnew = zoomed.values[0].sort(cmp); + assert[assertExtent[inOut][0]](+xnew[0], +new Date(xold[0])); + assert[assertExtent[inOut][1]](+xnew[1], +new Date(xold[1])); + await testRender(`temporal_${inOut}-1`); + } + }); - if (bind === unbound) { - it('should work with ordinal/nominal domains', async () => { + it('should work with log/pow scales', async () => { for (let i = 0; i < hits.zoom.length; i++) { await embed( spec( @@ -157,47 +143,91 @@ for (const bind of [bound, unbound]) { i, {type, ...binding}, { - x: {type: 'ordinal'}, - y: {type: 'nominal'} + x: {scale: {type: 'pow', exponent: 1.5}}, + y: {scale: {type: 'log'}} } ) ); const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); - await testRender(`ord_${inOut}-0`); + await testRender(`logpow_${inOut}-0`); const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); - - if (inOut === 'in') { - expect(xnew.length).toBeLessThanOrEqual(xold.length); - expect(ynew.length).toBeLessThanOrEqual(yold.length); - } else { - expect(xnew.length).toBeGreaterThanOrEqual(xold.length); - expect(ynew.length).toBeGreaterThanOrEqual(yold.length); - } - - await testRender(`ord_${inOut}-1`); + assert[assertExtent[inOut][0]](xnew[0], xold[0]); + assert[assertExtent[inOut][1]](xnew[1], xold[1]); + assert[assertExtent[inOut][0]](ynew[0], yold[0]); + assert[assertExtent[inOut][1]](ynew[1], yold[1]); + await testRender(`logpow_${inOut}-1`); } }); - } else { - for (const specType of compositeTypes) { - it(`should work with shared scales in ${specType} views`, async () => { - for (let i = 0; i < hits.bins.length; i++) { - await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); - const parent = parentSelector(specType, i); - const {inOut, xold, yold} = await setup(specType, i, ['x', 'y'], parent); - const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, bind === unbound)))[0]; + + if (bind === unbound) { + it('should work with ordinal/nominal domains', async () => { + for (let i = 0; i < hits.zoom.length; i++) { + await embed( + spec( + 'unit', + i, + {type, ...binding}, + { + x: {type: 'ordinal'}, + y: {type: 'nominal'} + } + ) + ); + const {inOut, xold, yold} = await setup('drag', i, ['x', 'y']); + await testRender(`ord_${inOut}-0`); + + const zoomed = (await page.evaluate(zoom('zoom', i, inOut, null, bind === unbound)))[0]; const xnew = zoomed.values[0].sort(cmp); const ynew = zoomed.values[1].sort(cmp); - assert[assertExtent[inOut][0]](xnew[0], xold[0]); - assert[assertExtent[inOut][1]](xnew[1], xold[1]); - assert[assertExtent[inOut][0]](ynew[0], yold[0]); - assert[assertExtent[inOut][1]](ynew[1], yold[1]); - await testRender(`${specType}_${inOut}`); + + if (inOut === 'in') { + expect(xnew.length).toBeLessThanOrEqual(xold.length); + expect(ynew.length).toBeLessThanOrEqual(yold.length); + } else { + expect(xnew.length).toBeGreaterThanOrEqual(xold.length); + expect(ynew.length).toBeGreaterThanOrEqual(yold.length); + } + + await testRender(`ord_${inOut}-1`); } }); + } else { + for (const specType of compositeTypes) { + it(`should work with shared scales in ${specType} views`, async () => { + for (let i = 0; i < hits.bins.length; i++) { + await embed(spec(specType, 0, {type, ...binding}, {resolve: {scale: {x: 'shared', y: 'shared'}}})); + const parent = parentSelector(specType, i); + const {inOut, xold, yold} = await setup(specType, i, ['x', 'y'], parent); + const zoomed = (await page.evaluate(zoom('bins', i, inOut, null, bind === unbound)))[0]; + const xnew = zoomed.values[0].sort(cmp); + const ynew = zoomed.values[1].sort(cmp); + assert[assertExtent[inOut][0]](xnew[0], xold[0]); + assert[assertExtent[inOut][1]](xnew[1], xold[1]); + assert[assertExtent[inOut][0]](ynew[0], yold[0]); + assert[assertExtent[inOut][1]](ynew[1], yold[1]); + await testRender(`${specType}_${inOut}`); + } + }); + } } + }); + } + + it('should work with geo intervals', async () => { + testRender = testRenderFn(page, `interval/zoom`); + + await embed(geoSpec()); + const drag = await page.evaluate(brush('drag', 1)); + expect(drag).toHaveLength(13); + await testRender(`geo-0`); + + for (let i = 0; i < hits.zoom.length; i++) { + const zoomed = await page.evaluate(zoom('zoom', i, i % 2 ? 'out' : 'in', null, true)); + expect(zoomed.length).toBeGreaterThan(0); + await testRender(`geo-${i + 1}`); } }); -} +});