From 4b711372c511fc8560bc05d440f158fb2d4a0fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 18 Nov 2020 15:18:55 +0100 Subject: [PATCH] Tests: Adds expects for observables (#28929) * Tests: Adds expects for observables * Refactor: renames matcher * Chore: changed jest version and removed as dev dependency * Refactor: Changes after PR review * wip * Chore: revert * Chore: Adds jest-matcher-utils * Chore: fixed merge error * Tests: Removed inline snapshots as they are env dependent * Tests: missed a snapshot * Refactor: Removed type file after PR comments --- package.json | 1 + .../transformers/calculateField.test.ts | 182 ++++--- .../transformers/ensureColumns.test.ts | 142 +++--- .../transformers/filter.test.ts | 16 +- .../transformers/filterByName.test.ts | 149 +++--- .../transformers/filterByRefId.test.ts | 23 +- .../transformers/groupBy.test.ts | 210 ++++---- .../transformers/labelsToFields.test.ts | 92 ++-- .../transformers/merge.test.ts | 392 +++++++-------- .../transformers/order.test.ts | 190 ++++---- .../transformers/organize.test.ts | 111 ++--- .../transformers/reduce.test.ts | 185 ++++--- .../transformers/rename.test.ts | 212 ++++---- .../transformers/seriesToColumns.test.ts | 451 +++++++++--------- .../transformers/seriesToRows.test.ts | 145 +++--- packages/grafana-data/tsconfig.json | 7 +- packages/grafana-data/typings/jest/index.d.ts | 12 + .../standardAnnotationSupport.test.ts | 150 +++--- .../datasource/loki/datasource.test.ts | 272 +++++------ .../postgres/specs/datasource.test.ts | 16 +- public/test/matchers/index.ts | 9 + public/test/matchers/toEmitValues.test.ts | 139 ++++++ public/test/matchers/toEmitValues.ts | 88 ++++ public/test/matchers/toEmitValuesWith.test.ts | 153 ++++++ public/test/matchers/toEmitValuesWith.ts | 56 +++ public/test/matchers/types.ts | 13 + public/test/matchers/utils.ts | 62 +++ public/test/setupTests.ts | 3 + tsconfig.json | 3 +- yarn.lock | 12 +- 30 files changed, 1889 insertions(+), 1607 deletions(-) create mode 100644 packages/grafana-data/typings/jest/index.d.ts create mode 100644 public/test/matchers/index.ts create mode 100644 public/test/matchers/toEmitValues.test.ts create mode 100644 public/test/matchers/toEmitValues.ts create mode 100644 public/test/matchers/toEmitValuesWith.test.ts create mode 100644 public/test/matchers/toEmitValuesWith.ts create mode 100644 public/test/matchers/types.ts create mode 100644 public/test/matchers/utils.ts diff --git a/package.json b/package.json index 8f2e5b5c5d0..e52352e6277 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,7 @@ "jest": "26.4.2", "jest-canvas-mock": "2.2.0", "jest-date-mock": "1.0.8", + "jest-matcher-utils": "26.0.0", "lerna": "^3.20.2", "lint-staged": "10.0.7", "load-grunt-tasks": "5.1.0", diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts index 13fd0bc80ed..ce9e3d3c5f6 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts @@ -7,7 +7,6 @@ import { transformDataFrame } from '../transformDataFrame'; import { CalculateFieldMode, calculateFieldTransformer, ReduceOptions } from './calculateField'; import { DataFrameView } from '../../dataframe'; import { BinaryOperationID } from '../../utils'; -import { observableTester } from '../../utils/tests/observableTester'; const seriesA = toDataFrame({ fields: [ @@ -30,7 +29,7 @@ describe('calculateField transformer w/ timeseries', () => { mockTransformationsRegistry([calculateFieldTransformer]); }); - it('will filter and alias', done => { + it('will filter and alias', async () => { const cfg = { id: DataTransformerID.calculateField, options: { @@ -39,35 +38,32 @@ describe('calculateField transformer w/ timeseries', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesBC]), - expect: data => { - const filtered = data[0]; - const rows = new DataFrameView(filtered).toArray(); - expect(rows).toEqual([ - { - A: 1, - B: 2, - C: 3, - D: 'first', - 'The Total': 6, - TheTime: 1000, - }, - { - A: 100, - B: 200, - C: 300, - D: 'second', - 'The Total': 600, - TheTime: 2000, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toEqual([ + { + A: 1, + B: 2, + C: 3, + D: 'first', + 'The Total': 6, + TheTime: 1000, + }, + { + A: 100, + B: 200, + C: 300, + D: 'second', + 'The Total': 600, + TheTime: 2000, + }, + ]); }); }); - it('will replace other fields', done => { + it('will replace other fields', async () => { const cfg = { id: DataTransformerID.calculateField, options: { @@ -79,27 +75,24 @@ describe('calculateField transformer w/ timeseries', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesBC]), - expect: data => { - const filtered = data[0]; - const rows = new DataFrameView(filtered).toArray(); - expect(rows).toEqual([ - { - Mean: 2, - TheTime: 1000, - }, - { - Mean: 200, - TheTime: 2000, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toEqual([ + { + Mean: 2, + TheTime: 1000, + }, + { + Mean: 200, + TheTime: 2000, + }, + ]); }); }); - it('will filter by name', done => { + it('will filter by name', async () => { const cfg = { id: DataTransformerID.calculateField, options: { @@ -112,27 +105,24 @@ describe('calculateField transformer w/ timeseries', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesBC]), - expect: data => { - const filtered = data[0]; - const rows = new DataFrameView(filtered).toArray(); - expect(rows).toEqual([ - { - Mean: 2, - TheTime: 1000, - }, - { - Mean: 200, - TheTime: 2000, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [seriesBC])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toEqual([ + { + Mean: 2, + TheTime: 1000, + }, + { + Mean: 200, + TheTime: 2000, + }, + ]); }); }); - it('binary math', done => { + it('binary math', async () => { const cfg = { id: DataTransformerID.calculateField, options: { @@ -146,27 +136,24 @@ describe('calculateField transformer w/ timeseries', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesBC]), - expect: data => { - const filtered = data[0]; - const rows = new DataFrameView(filtered).toArray(); - expect(rows).toEqual([ - { - 'B + C': 5, - TheTime: 1000, - }, - { - 'B + C': 500, - TheTime: 2000, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [seriesBC])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toEqual([ + { + 'B + C': 5, + TheTime: 1000, + }, + { + 'B + C': 500, + TheTime: 2000, + }, + ]); }); }); - it('field + static number', done => { + it('field + static number', async () => { const cfg = { id: DataTransformerID.calculateField, options: { @@ -180,23 +167,20 @@ describe('calculateField transformer w/ timeseries', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesBC]), - expect: data => { - const filtered = data[0]; - const rows = new DataFrameView(filtered).toArray(); - expect(rows).toEqual([ - { - 'B + 2': 4, - TheTime: 1000, - }, - { - 'B + 2': 202, - TheTime: 2000, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [seriesBC])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toEqual([ + { + 'B + 2': 4, + TheTime: 1000, + }, + { + 'B + 2': 202, + TheTime: 2000, + }, + ]); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts index 02df078b203..8fcb1352f3d 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts @@ -5,7 +5,6 @@ import { mockTransformationsRegistry } from '../../utils/tests/mockTransformatio import { transformDataFrame } from '../transformDataFrame'; import { ensureColumnsTransformer } from './ensureColumns'; import { seriesToColumnsTransformer } from './seriesToColumns'; -import { observableTester } from '../../utils/tests/observableTester'; const seriesA = toDataFrame({ fields: [ @@ -36,7 +35,7 @@ describe('ensureColumns transformer', () => { mockTransformationsRegistry([ensureColumnsTransformer, seriesToColumnsTransformer]); }); - it('will transform to columns if time field exists and multiple frames', done => { + it('will transform to columns if time field exists and multiple frames', async () => { const cfg = { id: DataTransformerID.ensureColumns, options: {}, @@ -44,79 +43,62 @@ describe('ensureColumns transformer', () => { const data = [seriesA, seriesBC]; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], data), - expect: filtered => { - expect(filtered.length).toEqual(1); - expect(filtered[0]).toMatchInlineSnapshot(` - Object { - "fields": Array [ - Object { - "config": Object {}, - "labels": undefined, - "name": "TheTime", - "type": "time", - "values": Array [ - 1000, - 2000, - ], - }, - Object { - "config": Object {}, - "labels": Object {}, - "name": "A", - "type": "number", - "values": Array [ - 1, - 100, - ], - }, - Object { - "config": Object {}, - "labels": Object {}, - "name": "B", - "type": "number", - "values": Array [ - 2, - 200, - ], - }, - Object { - "config": Object {}, - "labels": Object {}, - "name": "C", - "type": "number", - "values": Array [ - 3, - 300, - ], - }, - Object { - "config": Object {}, - "labels": Object {}, - "name": "D", - "type": "string", - "values": Array [ - "first", - "second", - ], - }, - ], - "meta": Object { - "transformations": Array [ - "ensureColumns", + await expect(transformDataFrame([cfg], data)).toEmitValuesWith(received => { + const filtered = received[0]; + expect(filtered.length).toEqual(1); + + const frame = filtered[0]; + expect(frame.fields.length).toEqual(5); + expect(filtered[0]).toEqual( + toDataFrame({ + fields: [ + { + name: 'TheTime', + type: 'time', + config: {}, + values: [1000, 2000], + labels: undefined, + }, + { + name: 'A', + type: 'number', + config: {}, + values: [1, 100], + labels: {}, + }, + { + name: 'B', + type: 'number', + config: {}, + values: [2, 200], + labels: {}, + }, + { + name: 'C', + type: 'number', + config: {}, + values: [3, 300], + labels: {}, + }, + { + name: 'D', + type: 'string', + config: {}, + values: ['first', 'second'], + labels: {}, + }, ], - }, - "name": undefined, - "refId": undefined, - } - `); - }, - done, + meta: { + transformations: ['ensureColumns'], + }, + name: undefined, + refId: undefined, + }) + ); }); }); - it('will not transform to columns if time field is missing for any of the series', done => { + it('will not transform to columns if time field is missing for any of the series', async () => { const cfg = { id: DataTransformerID.ensureColumns, options: {}, @@ -124,16 +106,10 @@ describe('ensureColumns transformer', () => { const data = [seriesBC, seriesNoTime]; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], data), - expect: filtered => { - expect(filtered).toEqual(data); - }, - done, - }); + await expect(transformDataFrame([cfg], data)).toEmitValues([data]); }); - it('will not transform to columns if only one series', done => { + it('will not transform to columns if only one series', async () => { const cfg = { id: DataTransformerID.ensureColumns, options: {}, @@ -141,12 +117,6 @@ describe('ensureColumns transformer', () => { const data = [seriesBC]; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], data), - expect: filtered => { - expect(filtered).toEqual(data); - }, - done, - }); + await expect(transformDataFrame([cfg], data)).toEmitValues([data]); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/filter.test.ts b/packages/grafana-data/src/transformations/transformers/filter.test.ts index f22af4fb879..b6994330211 100644 --- a/packages/grafana-data/src/transformations/transformers/filter.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filter.test.ts @@ -5,7 +5,6 @@ import { FieldMatcherID } from '../matchers/ids'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { filterFieldsTransformer } from './filter'; import { transformDataFrame } from '../transformDataFrame'; -import { observableTester } from '../../utils/tests/observableTester'; export const simpleSeriesWithTypes = toDataFrame({ fields: [ @@ -21,7 +20,7 @@ describe('Filter Transformer', () => { mockTransformationsRegistry([filterFieldsTransformer]); }); - it('filters by include', done => { + it('filters by include', async () => { const cfg = { id: DataTransformerID.filterFields, options: { @@ -29,14 +28,11 @@ describe('Filter Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [simpleSeriesWithTypes]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(1); - expect(filtered.fields[0].name).toBe('D'); - }, - done, + await expect(transformDataFrame([cfg], [simpleSeriesWithTypes])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(1); + expect(filtered.fields[0].name).toBe('D'); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts index 4e23a58f177..28422415859 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts @@ -5,7 +5,6 @@ import { mockTransformationsRegistry } from '../../utils/tests/mockTransformatio import { filterFieldsByNameTransformer } from './filterByName'; import { filterFieldsTransformer } from './filter'; import { transformDataFrame } from '../transformDataFrame'; -import { observableTester } from '../../utils/tests/observableTester'; export const seriesWithNamesToMatch = toDataFrame({ fields: [ @@ -21,24 +20,21 @@ describe('filterByName transformer', () => { mockTransformationsRegistry([filterFieldsByNameTransformer, filterFieldsTransformer]); }); - it('returns original series if no options provided', done => { + it('returns original series if no options provided', async () => { const cfg = { id: DataTransformerID.filterFields, options: {}, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(4); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(4); }); }); describe('respects', () => { - it('inclusion by pattern', done => { + it('inclusion by pattern', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -48,18 +44,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('startsWithA'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('startsWithA'); }); }); - it('exclusion by pattern', done => { + it('exclusion by pattern', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -69,18 +62,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('B'); }); }); - it('inclusion and exclusion by pattern', done => { + it('inclusion and exclusion by pattern', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -89,18 +79,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(1); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(1); + expect(filtered.fields[0].name).toBe('B'); }); }); - it('inclusion by names', done => { + it('inclusion by names', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -110,18 +97,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('startsWithA'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('startsWithA'); }); }); - it('exclusion by names', done => { + it('exclusion by names', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -131,18 +115,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('B'); }); }); - it('inclusion and exclusion by names', done => { + it('inclusion and exclusion by names', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -151,18 +132,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(1); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(1); + expect(filtered.fields[0].name).toBe('B'); }); }); - it('inclusion by both', done => { + it('inclusion by both', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -173,18 +151,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('startsWithA'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('startsWithA'); }); }); - it('exclusion by both', done => { + it('exclusion by both', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -195,18 +170,15 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(2); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(2); + expect(filtered.fields[0].name).toBe('B'); }); }); - it('inclusion and exclusion by both', done => { + it('inclusion and exclusion by both', async () => { const cfg = { id: DataTransformerID.filterFieldsByName, options: { @@ -215,14 +187,11 @@ describe('filterByName transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithNamesToMatch]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields.length).toBe(1); - expect(filtered.fields[0].name).toBe('B'); - }, - done, + await expect(transformDataFrame([cfg], [seriesWithNamesToMatch])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields.length).toBe(1); + expect(filtered.fields[0].name).toBe('B'); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts index 18ee3f94b3d..ce614a97a8a 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts @@ -3,7 +3,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { filterFramesByRefIdTransformer } from './filterByRefId'; import { transformDataFrame } from '../transformDataFrame'; -import { observableTester } from '../../utils/tests/observableTester'; export const allSeries = [ toDataFrame({ @@ -25,23 +24,20 @@ describe('filterByRefId transformer', () => { mockTransformationsRegistry([filterFramesByRefIdTransformer]); }); - it('returns all series if no options provided', done => { + it('returns all series if no options provided', async () => { const cfg = { id: DataTransformerID.filterByRefId, options: {}, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], allSeries), - expect: filtered => { - expect(filtered.length).toBe(3); - }, - done, + await expect(transformDataFrame([cfg], allSeries)).toEmitValuesWith(received => { + const filtered = received[0]; + expect(filtered.length).toBe(3); }); }); describe('respects', () => { - it('inclusion', done => { + it('inclusion', async () => { const cfg = { id: DataTransformerID.filterByRefId, options: { @@ -49,12 +45,9 @@ describe('filterByRefId transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], allSeries), - expect: filtered => { - expect(filtered.map(f => f.refId)).toEqual(['A', 'B']); - }, - done, + await expect(transformDataFrame([cfg], allSeries)).toEmitValuesWith(received => { + const filtered = received[0]; + expect(filtered.map(f => f.refId)).toEqual(['A', 'B']); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts index acd465e2e25..7d599983a05 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts @@ -7,14 +7,13 @@ import { DataTransformerID } from './ids'; import { ArrayVector } from '../../vector'; import { ReducerID } from '../fieldReducer'; import { DataTransformerConfig } from '@grafana/data'; -import { observableTester } from '../../utils/tests/observableTester'; describe('GroupBy transformer', () => { beforeAll(() => { mockTransformationsRegistry([groupByTransformer]); }); - it('should not apply transformation if config is missing group by fields', done => { + it('should not apply transformation if config is missing group by fields', async () => { const testSeries = toDataFrame({ name: 'A', fields: [ @@ -36,16 +35,13 @@ describe('GroupBy transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [testSeries]), - expect: result => { - expect(result[0]).toBe(testSeries); - }, - done, + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith(received => { + const result = received[0]; + expect(result[0]).toBe(testSeries); }); }); - it('should group values by message', done => { + it('should group values by message', async () => { const testSeries = toDataFrame({ name: 'A', fields: [ @@ -67,25 +63,22 @@ describe('GroupBy transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [testSeries]), - expect: result => { - const expected: Field[] = [ - { - name: 'message', - type: FieldType.string, - values: new ArrayVector(['one', 'two', 'three']), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'three']), + config: {}, + }, + ]; - expect(result[0].fields).toEqual(expected); - }, - done, + expect(result[0].fields).toEqual(expected); }); }); - it('should group values by message and summarize values', done => { + it('should group values by message and summarize values', async () => { const testSeries = toDataFrame({ name: 'A', fields: [ @@ -111,31 +104,28 @@ describe('GroupBy transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [testSeries]), - expect: result => { - const expected: Field[] = [ - { - name: 'message', - type: FieldType.string, - values: new ArrayVector(['one', 'two', 'three']), - config: {}, - }, - { - name: 'values (sum)', - type: FieldType.number, - values: new ArrayVector([1, 4, 9]), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'three']), + config: {}, + }, + { + name: 'values (sum)', + type: FieldType.number, + values: new ArrayVector([1, 4, 9]), + config: {}, + }, + ]; - expect(result[0].fields).toEqual(expected); - }, - done, + expect(result[0].fields).toEqual(expected); }); }); - it('should group by and compute a few calculations for each group of values', done => { + it('should group by and compute a few calculations for each group of values', async () => { const testSeries = toDataFrame({ name: 'A', fields: [ @@ -165,43 +155,40 @@ describe('GroupBy transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [testSeries]), - expect: result => { - const expected: Field[] = [ - { - name: 'message', - type: FieldType.string, - values: new ArrayVector(['one', 'two', 'three']), - config: {}, - }, - { - name: 'time (count)', - type: FieldType.number, - values: new ArrayVector([1, 2, 3]), - config: {}, - }, - { - name: 'time (last)', - type: FieldType.time, - values: new ArrayVector([3000, 5000, 8000]), - config: {}, - }, - { - name: 'values (sum)', - type: FieldType.number, - values: new ArrayVector([1, 4, 9]), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'three']), + config: {}, + }, + { + name: 'time (count)', + type: FieldType.number, + values: new ArrayVector([1, 2, 3]), + config: {}, + }, + { + name: 'time (last)', + type: FieldType.time, + values: new ArrayVector([3000, 5000, 8000]), + config: {}, + }, + { + name: 'values (sum)', + type: FieldType.number, + values: new ArrayVector([1, 4, 9]), + config: {}, + }, + ]; - expect(result[0].fields).toEqual(expected); - }, - done, + expect(result[0].fields).toEqual(expected); }); }); - it('should group values in data frames induvidually', done => { + it('should group values in data frames individually', async () => { const testSeries = [ toDataFrame({ name: 'A', @@ -237,43 +224,40 @@ describe('GroupBy transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], testSeries), - expect: result => { - const expectedA: Field[] = [ - { - name: 'message', - type: FieldType.string, - values: new ArrayVector(['one', 'two', 'three']), - config: {}, - }, - { - name: 'values (sum)', - type: FieldType.number, - values: new ArrayVector([1, 4, 9]), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], testSeries)).toEmitValuesWith(received => { + const result = received[0]; + const expectedA: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'three']), + config: {}, + }, + { + name: 'values (sum)', + type: FieldType.number, + values: new ArrayVector([1, 4, 9]), + config: {}, + }, + ]; - const expectedB: Field[] = [ - { - name: 'message', - type: FieldType.string, - values: new ArrayVector(['one', 'two', 'three']), - config: {}, - }, - { - name: 'values (sum)', - type: FieldType.number, - values: new ArrayVector([0, 7, 8]), - config: {}, - }, - ]; + const expectedB: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'three']), + config: {}, + }, + { + name: 'values (sum)', + type: FieldType.number, + values: new ArrayVector([0, 7, 8]), + config: {}, + }, + ]; - expect(result[0].fields).toEqual(expectedA); - expect(result[1].fields).toEqual(expectedB); - }, - done, + expect(result[0].fields).toEqual(expectedA); + expect(result[1].fields).toEqual(expectedB); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts index 0e57d2d5e5a..68b838a546f 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts @@ -4,14 +4,13 @@ import { DataTransformerConfig, FieldDTO, FieldType } from '../../types'; import { DataTransformerID } from './ids'; import { toDataFrame, toDataFrameDTO } from '../../dataframe'; import { transformDataFrame } from '../transformDataFrame'; -import { observableTester } from '../../utils/tests/observableTester'; describe('Labels as Columns', () => { beforeAll(() => { mockTransformationsRegistry([labelsToFieldsTransformer]); }); - it('data frame with two labels', done => { + it('data frame with two labels', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.labelsToFields, options: {}, @@ -25,30 +24,27 @@ describe('Labels as Columns', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [source]), - expect: data => { - const result = toDataFrameDTO(data[0]); + await expect(transformDataFrame([cfg], [source])).toEmitValuesWith(received => { + const data = received[0]; + const result = toDataFrameDTO(data[0]); - const expected: FieldDTO[] = [ - { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, - { - name: 'location', - type: FieldType.string, - values: ['inside', 'inside'], - config: {}, - }, - { name: 'feelsLike', type: FieldType.string, values: ['ok', 'ok'], config: {} }, - { name: 'Value', type: FieldType.number, values: [1, 2], config: {} }, - ]; + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { + name: 'location', + type: FieldType.string, + values: ['inside', 'inside'], + config: {}, + }, + { name: 'feelsLike', type: FieldType.string, values: ['ok', 'ok'], config: {} }, + { name: 'Value', type: FieldType.number, values: [1, 2], config: {} }, + ]; - expect(result.fields).toEqual(expected); - }, - done, + expect(result.fields).toEqual(expected); }); }); - it('data frame with two labels and valueLabel option', done => { + it('data frame with two labels and valueLabel option', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.labelsToFields, options: { valueLabel: 'name' }, @@ -71,29 +67,26 @@ describe('Labels as Columns', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [source]), - expect: data => { - const result = toDataFrameDTO(data[0]); + await expect(transformDataFrame([cfg], [source])).toEmitValuesWith(received => { + const data = received[0]; + const result = toDataFrameDTO(data[0]); - const expected: FieldDTO[] = [ - { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, - { - name: 'location', - type: FieldType.string, - values: ['inside', 'inside'], - config: {}, - }, - { name: 'Request', type: FieldType.number, values: [1, 2], config: {} }, - ]; + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { + name: 'location', + type: FieldType.string, + values: ['inside', 'inside'], + config: {}, + }, + { name: 'Request', type: FieldType.number, values: [1, 2], config: {} }, + ]; - expect(result.fields).toEqual(expected); - }, - done, + expect(result.fields).toEqual(expected); }); }); - it('two data frames with 1 value and 1 label', done => { + it('two data frames with 1 value and 1 label', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.labelsToFields, options: {}, @@ -115,20 +108,17 @@ describe('Labels as Columns', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [oneValueOneLabelA, oneValueOneLabelB]), - expect: data => { - const result = toDataFrameDTO(data[0]); + await expect(transformDataFrame([cfg], [oneValueOneLabelA, oneValueOneLabelB])).toEmitValuesWith(received => { + const data = received[0]; + const result = toDataFrameDTO(data[0]); - const expected: FieldDTO[] = [ - { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, - { name: 'location', type: FieldType.string, values: ['inside', 'outside'], config: {} }, - { name: 'temp', type: FieldType.number, values: [1, -1], config: {} }, - ]; + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { name: 'location', type: FieldType.string, values: ['inside', 'outside'], config: {} }, + { name: 'temp', type: FieldType.number, values: [1, -1], config: {} }, + ]; - expect(result.fields).toEqual(expected); - }, - done, + expect(result.fields).toEqual(expected); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/merge.test.ts b/packages/grafana-data/src/transformations/transformers/merge.test.ts index 4f4e21716d6..1be192e37ec 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.test.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.test.ts @@ -5,9 +5,8 @@ import { toDataFrame } from '../../dataframe'; import { transformDataFrame } from '../transformDataFrame'; import { ArrayVector } from '../../vector'; import { mergeTransformer, MergeTransformerOptions } from './merge'; -import { observableTester } from '../../utils/tests/observableTester'; -describe('Merge multipe to single', () => { +describe('Merge multiple to single', () => { const cfg: DataTransformerConfig = { id: DataTransformerID.merge, options: {}, @@ -17,7 +16,7 @@ describe('Merge multipe to single', () => { mockTransformationsRegistry([mergeTransformer]); }); - it('combine two series into one', done => { + it('combine two series into one', async () => { const seriesA = toDataFrame({ name: 'A', fields: [ @@ -34,21 +33,18 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [1000, 2000]), - createField('Temp', FieldType.number, [1, -1]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [1000, 2000]), + createField('Temp', FieldType.number, [1, -1]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two series with multiple values into one', done => { + it('combine two series with multiple values into one', async () => { const seriesA = toDataFrame({ name: 'A', fields: [ @@ -65,21 +61,18 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine three series into one', done => { + it('combine three series into one', async () => { const seriesA = toDataFrame({ name: 'A', fields: [ @@ -104,21 +97,18 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB, seriesC]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [1000, 2000, 500]), - createField('Temp', FieldType.number, [1, -1, 2]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB, seriesC])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [1000, 2000, 500]), + createField('Temp', FieldType.number, [1, -1, 2]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine one serie and two tables into one table', done => { + it('combine one serie and two tables into one table', async () => { const tableA = toDataFrame({ name: 'A', fields: [ @@ -145,22 +135,19 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [tableA, seriesB, tableB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [1000, 1000, 500]), - createField('Temp', FieldType.number, [1, -1, 2]), - createField('Humidity', FieldType.number, [10, null, 5]), - ]; + await expect(transformDataFrame([cfg], [tableA, seriesB, tableB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [1000, 1000, 500]), + createField('Temp', FieldType.number, [1, -1, 2]), + createField('Humidity', FieldType.number, [10, null, 5]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine one serie and two tables with ISO dates into one table', done => { + it('combine one serie and two tables with ISO dates into one table', async () => { const tableA = toDataFrame({ name: 'A', fields: [ @@ -187,22 +174,19 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [tableA, seriesB, tableC]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, ['2019-10-01T11:10:23Z', '2019-09-01T11:10:23Z', '2019-11-01T11:10:23Z']), - createField('Temp', FieldType.number, [1, -1, 2]), - createField('Humidity', FieldType.number, [10, null, 5]), - ]; + await expect(transformDataFrame([cfg], [tableA, seriesB, tableC])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, ['2019-10-01T11:10:23Z', '2019-09-01T11:10:23Z', '2019-11-01T11:10:23Z']), + createField('Temp', FieldType.number, [1, -1, 2]), + createField('Humidity', FieldType.number, [10, null, 5]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two tables, where first is partial overlapping, into one', done => { + it('combine two tables, where first is partial overlapping, into one', async () => { const tableA = toDataFrame({ name: 'A', fields: [ @@ -228,39 +212,36 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [tableA, tableB]), - expect: result => { - const expected: Field[] = [ - createField('Country', FieldType.string, [ - 'United States', - 'United States', - 'Mexico', - 'Germany', - 'Canada', - 'Canada', - null, - ]), - createField('AgeGroup', FieldType.string, [ - '50 or over', - '35 - 49', - '0 - 17', - '35 - 49', - '35 - 49', - '25 - 34', - '18 - 24', - ]), - createField('Sum', FieldType.number, [998, 1193, 1675, 146, 166, 219, null]), - createField('Count', FieldType.number, [2, 4, 1, 4, 4, 2, 3]), - ]; + await expect(transformDataFrame([cfg], [tableA, tableB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Country', FieldType.string, [ + 'United States', + 'United States', + 'Mexico', + 'Germany', + 'Canada', + 'Canada', + null, + ]), + createField('AgeGroup', FieldType.string, [ + '50 or over', + '35 - 49', + '0 - 17', + '35 - 49', + '35 - 49', + '25 - 34', + '18 - 24', + ]), + createField('Sum', FieldType.number, [998, 1193, 1675, 146, 166, 219, null]), + createField('Count', FieldType.number, [2, 4, 1, 4, 4, 2, 3]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two tables, where second is partial overlapping, into one', done => { + it('combine two tables, where second is partial overlapping, into one', async () => { /** * This behavior feels wrong. I would expect the same behavior regardless of the order * of the frames. But when testing the old table panel it had this behavior so I am @@ -291,39 +272,36 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [tableA, tableB]), - expect: result => { - const expected: Field[] = [ - createField('AgeGroup', FieldType.string, [ - '0 - 17', - '18 - 24', - '25 - 34', - '35 - 49', - '50 or over', - '35 - 49', - '35 - 49', - ]), - createField('Count', FieldType.number, [1, 3, 2, 4, 2, null, null]), - createField('Country', FieldType.string, [ - 'Mexico', - null, - 'Canada', - 'United States', - 'United States', - 'Germany', - 'Canada', - ]), - createField('Sum', FieldType.number, [1675, null, 219, 1193, 998, 146, 166]), - ]; + await expect(transformDataFrame([cfg], [tableA, tableB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('AgeGroup', FieldType.string, [ + '0 - 17', + '18 - 24', + '25 - 34', + '35 - 49', + '50 or over', + '35 - 49', + '35 - 49', + ]), + createField('Count', FieldType.number, [1, 3, 2, 4, 2, null, null]), + createField('Country', FieldType.string, [ + 'Mexico', + null, + 'Canada', + 'United States', + 'United States', + 'Germany', + 'Canada', + ]), + createField('Sum', FieldType.number, [1675, null, 219, 1193, 998, 146, 166]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine three tables with multiple values into one', done => { + it('combine three tables with multiple values into one', async () => { const tableA = toDataFrame({ name: 'A', fields: [ @@ -351,23 +329,20 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [tableA, tableB, tableC]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126, 100, 124, 149]), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3, 1, 4, 5]), - createField('Humidity', FieldType.number, [10, 14, 55, null, null, null, 22, 25, 30]), - createField('Enabled', FieldType.boolean, [null, null, null, true, false, true, null, null, null]), - ]; + await expect(transformDataFrame([cfg], [tableA, tableB, tableC])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126, 100, 124, 149]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3, 1, 4, 5]), + createField('Humidity', FieldType.number, [10, 14, 55, null, null, null, 22, 25, 30]), + createField('Enabled', FieldType.boolean, [null, null, null, true, false, true, null, null, null]), + ]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two time series, where first serie fields has displayName, into one', done => { + it('combine two time series, where first serie fields has displayName, into one', async () => { const serieA = toDataFrame({ name: 'A', fields: [ @@ -384,24 +359,21 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[1].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + expect(fields[1].config).toEqual({}); + expect(fields).toEqual(expected); }); }); - it('combine two time series, where first serie fields has display processor, into one', done => { + it('combine two time series, where first serie fields has display processor, into one', async () => { const displayProcessor: DisplayProcessor = jest.fn(); const serieA = toDataFrame({ @@ -420,24 +392,21 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126], {}, displayProcessor), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126], {}, displayProcessor), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[0].display).toBe(displayProcessor); - expect(fields).toEqual(expected); - }, - done, + expect(fields[0].display).toBe(displayProcessor); + expect(fields).toEqual(expected); }); }); - it('combine two time series, where first serie fields has units, into one', done => { + it('combine two time series, where first serie fields has units, into one', async () => { const serieA = toDataFrame({ name: 'A', fields: [ @@ -454,24 +423,21 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3], { units: 'celsius' }), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3], { units: 'celsius' }), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[1].config).toEqual({ units: 'celsius' }); - expect(fields).toEqual(expected); - }, - done, + expect(fields[1].config).toEqual({ units: 'celsius' }); + expect(fields).toEqual(expected); }); }); - it('combine two time series, where second serie fields has units, into one', done => { + it('combine two time series, where second serie fields has units, into one', async () => { const serieA = toDataFrame({ name: 'A', fields: [ @@ -488,24 +454,21 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), - createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[1].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + expect(fields[1].config).toEqual({}); + expect(fields).toEqual(expected); }); }); - it('combine one regular serie with an empty serie should return the regular serie', done => { + it('combine one regular serie with an empty serie should return the regular serie', async () => { const serieA = toDataFrame({ name: 'A', fields: [ @@ -519,24 +482,21 @@ describe('Merge multipe to single', () => { fields: [], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200]), - createField('Temp', FieldType.number, [1, 4, 5]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200]), + createField('Temp', FieldType.number, [1, 4, 5]), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[1].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + expect(fields[1].config).toEqual({}); + expect(fields).toEqual(expected); }); }); - it('combine two regular series with an empty serie should return the combination of the regular series', done => { + it('combine two regular series with an empty serie should return the combination of the regular series', async () => { const serieA = toDataFrame({ name: 'A', fields: [ @@ -558,25 +518,22 @@ describe('Merge multipe to single', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB, serieC]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [100, 150, 200]), - createField('Temp', FieldType.number, [1, 4, 5]), - createField('Humidity', FieldType.number, [6, 7, 8]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB, serieC])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = [ + createField('Time', FieldType.time, [100, 150, 200]), + createField('Temp', FieldType.number, [1, 4, 5]), + createField('Humidity', FieldType.number, [6, 7, 8]), + ]; - const fields = unwrap(result[0].fields); + const fields = unwrap(result[0].fields); - expect(fields[1].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + expect(fields[1].config).toEqual({}); + expect(fields).toEqual(expected); }); }); - it('combine multiple empty series should return one empty serie', done => { + it('combine multiple empty series should return one empty serie', async () => { const serieA = toDataFrame({ name: 'A', fields: [], @@ -592,16 +549,13 @@ describe('Merge multipe to single', () => { fields: [], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB, serieC]), - expect: result => { - const expected: Field[] = []; - const fields = unwrap(result[0].fields); + await expect(transformDataFrame([cfg], [serieA, serieB, serieC])).toEmitValuesWith(received => { + const result = received[0]; + const expected: Field[] = []; + const fields = unwrap(result[0].fields); - expect(fields).toEqual(expected); - expect(result.length).toEqual(1); - }, - done, + expect(fields).toEqual(expected); + expect(result.length).toEqual(1); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/order.test.ts b/packages/grafana-data/src/transformations/transformers/order.test.ts index af7924b5288..4c529d0c992 100644 --- a/packages/grafana-data/src/transformations/transformers/order.test.ts +++ b/packages/grafana-data/src/transformations/transformers/order.test.ts @@ -8,7 +8,6 @@ import { } from '@grafana/data'; import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { observableTester } from '../../utils/tests/observableTester'; describe('Order Transformer', () => { beforeAll(() => { @@ -24,7 +23,7 @@ describe('Order Transformer', () => { ], }); - it('should order according to config', done => { + it('should order according to config', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.order, options: { @@ -36,44 +35,41 @@ describe('Order Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const ordered = data[0]; - expect(ordered.fields).toEqual([ - { - config: {}, - name: 'temperature', - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), - labels: undefined, - state: { - displayName: 'temperature', - }, + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const ordered = data[0]; + expect(ordered.fields).toEqual([ + { + config: {}, + name: 'temperature', + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + labels: undefined, + state: { + displayName: 'temperature', }, - { - config: {}, - name: 'humidity', - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), - labels: undefined, - state: { - displayName: 'humidity', - }, + }, + { + config: {}, + name: 'humidity', + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + labels: undefined, + state: { + displayName: 'humidity', }, - { - config: {}, - name: 'time', - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), - labels: undefined, - state: { - displayName: 'time', - }, + }, + { + config: {}, + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + labels: undefined, + state: { + displayName: 'time', }, - ]); - }, - done, + }, + ]); }); }); }); @@ -88,7 +84,7 @@ describe('Order Transformer', () => { ], }); - it('should append fields missing in config at the end', done => { + it('should append fields missing in config at the end', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.order, options: { @@ -100,44 +96,41 @@ describe('Order Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const ordered = data[0]; - expect(ordered.fields).toEqual([ - { - config: {}, - name: 'humidity', - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), - labels: undefined, - state: { - displayName: 'humidity', - }, + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const ordered = data[0]; + expect(ordered.fields).toEqual([ + { + config: {}, + name: 'humidity', + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + labels: undefined, + state: { + displayName: 'humidity', }, - { - config: {}, - name: 'time', - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), - labels: undefined, - state: { - displayName: 'time', - }, + }, + { + config: {}, + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + labels: undefined, + state: { + displayName: 'time', }, - { - config: {}, - name: 'pressure', - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), - labels: undefined, - state: { - displayName: 'pressure', - }, + }, + { + config: {}, + name: 'pressure', + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + labels: undefined, + state: { + displayName: 'pressure', }, - ]); - }, - done, + }, + ]); }); }); }); @@ -152,7 +145,7 @@ describe('Order Transformer', () => { ], }); - it('should keep the same order as in the incoming data', done => { + it('should keep the same order as in the incoming data', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.order, options: { @@ -160,32 +153,29 @@ describe('Order Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const ordered = data[0]; - expect(ordered.fields).toEqual([ - { - config: {}, - name: 'time', - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), - }, - { - config: {}, - name: 'pressure', - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), - }, - { - config: {}, - name: 'humidity', - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const ordered = data[0]; + expect(ordered.fields).toEqual([ + { + config: {}, + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + }, + { + config: {}, + name: 'pressure', + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + { + config: {}, + name: 'humidity', + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + }, + ]); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/organize.test.ts b/packages/grafana-data/src/transformations/transformers/organize.test.ts index 0274f0be97d..e14df0ca233 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.test.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.test.ts @@ -8,7 +8,6 @@ import { } from '@grafana/data'; import { organizeFieldsTransformer, OrganizeFieldsTransformerOptions } from './organize'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { observableTester } from '../../utils/tests/observableTester'; describe('OrganizeFields Transformer', () => { beforeAll(() => { @@ -25,7 +24,7 @@ describe('OrganizeFields Transformer', () => { ], }); - it('should order and filter according to config', done => { + it('should order and filter according to config', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.organize, options: { @@ -43,36 +42,33 @@ describe('OrganizeFields Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const organized = data[0]; - expect(organized.fields).toEqual([ - { - config: {}, - labels: undefined, - name: 'temperature', - state: { - displayName: 'temperature', - }, - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const organized = data[0]; + expect(organized.fields).toEqual([ + { + config: {}, + labels: undefined, + name: 'temperature', + state: { + displayName: 'temperature', }, - { - config: { - displayName: 'renamed_humidity', - }, - labels: undefined, - name: 'humidity', - state: { - displayName: 'renamed_humidity', - }, - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + { + config: { + displayName: 'renamed_humidity', }, - ]); - }, - done, + labels: undefined, + name: 'humidity', + state: { + displayName: 'renamed_humidity', + }, + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + }, + ]); }); }); }); @@ -87,7 +83,7 @@ describe('OrganizeFields Transformer', () => { ], }); - it('should append fields missing in config at the end', done => { + it('should append fields missing in config at the end', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.organize, options: { @@ -105,36 +101,33 @@ describe('OrganizeFields Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const organized = data[0]; - expect(organized.fields).toEqual([ - { - labels: undefined, - config: { - displayName: 'renamed_time', - }, - name: 'time', - state: { - displayName: 'renamed_time', - }, - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const organized = data[0]; + expect(organized.fields).toEqual([ + { + labels: undefined, + config: { + displayName: 'renamed_time', }, - { - config: {}, - labels: undefined, - name: 'pressure', - state: { - displayName: 'pressure', - }, - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + name: 'time', + state: { + displayName: 'renamed_time', }, - ]); - }, - done, + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + }, + { + config: {}, + labels: undefined, + name: 'pressure', + state: { + displayName: 'pressure', + }, + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + ]); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index c54ede357dc..01f444ba7c2 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -6,7 +6,6 @@ import { reduceFields, reduceTransformer } from './reduce'; import { transformDataFrame } from '../transformDataFrame'; import { Field, FieldType } from '../../types'; import { ArrayVector } from '../../vector'; -import { observableTester } from '../../utils/tests/observableTester'; import { notTimeFieldMatcher } from '../matchers/predicates'; import { DataFrameView } from '../../dataframe'; @@ -49,7 +48,7 @@ describe('Reducer Transformer', () => { mockTransformationsRegistry([reduceTransformer]); }); - it('reduces multiple data frames with many fields', done => { + it('reduces multiple data frames with many fields', async () => { const cfg = { id: DataTransformerID.reduce, options: { @@ -57,9 +56,9 @@ describe('Reducer Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesAWithMultipleFields, seriesBWithMultipleFields]), - expect: processed => { + await expect(transformDataFrame([cfg], [seriesAWithMultipleFields, seriesBWithMultipleFields])).toEmitValuesWith( + received => { + const processed = received[0]; const expected: Field[] = [ { name: 'Field', @@ -96,12 +95,11 @@ describe('Reducer Transformer', () => { expect(processed.length).toEqual(1); expect(processed[0].length).toEqual(4); expect(processed[0].fields).toEqual(expected); - }, - done, - }); + } + ); }); - it('reduces multiple data frames with single field', done => { + it('reduces multiple data frames with single field', async () => { const cfg = { id: DataTransformerID.reduce, options: { @@ -109,9 +107,9 @@ describe('Reducer Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesAWithSingleField, seriesBWithSingleField]), - expect: processed => { + await expect(transformDataFrame([cfg], [seriesAWithSingleField, seriesBWithSingleField])).toEmitValuesWith( + received => { + const processed = received[0]; const expected: Field[] = [ { name: 'Field', @@ -148,12 +146,11 @@ describe('Reducer Transformer', () => { expect(processed.length).toEqual(1); expect(processed[0].length).toEqual(2); expect(processed[0].fields).toEqual(expected); - }, - done, - }); + } + ); }); - it('reduces single data frame with many fields', done => { + it('reduces single data frame with many fields', async () => { const cfg = { id: DataTransformerID.reduce, options: { @@ -161,51 +158,48 @@ describe('Reducer Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesAWithMultipleFields]), - expect: processed => { - const expected: Field[] = [ - { - name: 'Field', - type: FieldType.string, - values: new ArrayVector(['A temperature', 'A humidity']), - config: {}, - }, - { - name: 'First', - type: FieldType.number, - values: new ArrayVector([3, 10000.3]), - config: {}, - }, - { - name: 'Min', - type: FieldType.number, - values: new ArrayVector([3, 10000.3]), - config: {}, - }, - { - name: 'Max', - type: FieldType.number, - values: new ArrayVector([6, 10000.6]), - config: {}, - }, - { - name: 'Last', - type: FieldType.number, - values: new ArrayVector([6, 10000.6]), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], [seriesAWithMultipleFields])).toEmitValuesWith(received => { + const processed = received[0]; + const expected: Field[] = [ + { + name: 'Field', + type: FieldType.string, + values: new ArrayVector(['A temperature', 'A humidity']), + config: {}, + }, + { + name: 'First', + type: FieldType.number, + values: new ArrayVector([3, 10000.3]), + config: {}, + }, + { + name: 'Min', + type: FieldType.number, + values: new ArrayVector([3, 10000.3]), + config: {}, + }, + { + name: 'Max', + type: FieldType.number, + values: new ArrayVector([6, 10000.6]), + config: {}, + }, + { + name: 'Last', + type: FieldType.number, + values: new ArrayVector([6, 10000.6]), + config: {}, + }, + ]; - expect(processed.length).toEqual(1); - expect(processed[0].length).toEqual(2); - expect(processed[0].fields).toEqual(expected); - }, - done, + expect(processed.length).toEqual(1); + expect(processed[0].length).toEqual(2); + expect(processed[0].fields).toEqual(expected); }); }); - it('reduces single data frame with single field', done => { + it('reduces single data frame with single field', async () => { const cfg = { id: DataTransformerID.reduce, options: { @@ -213,47 +207,44 @@ describe('Reducer Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesAWithSingleField]), - expect: processed => { - const expected: Field[] = [ - { - name: 'Field', - type: FieldType.string, - values: new ArrayVector(['A temperature']), - config: {}, - }, - { - name: 'First', - type: FieldType.number, - values: new ArrayVector([3]), - config: {}, - }, - { - name: 'Min', - type: FieldType.number, - values: new ArrayVector([3]), - config: {}, - }, - { - name: 'Max', - type: FieldType.number, - values: new ArrayVector([6]), - config: {}, - }, - { - name: 'Last', - type: FieldType.number, - values: new ArrayVector([6]), - config: {}, - }, - ]; + await expect(transformDataFrame([cfg], [seriesAWithSingleField])).toEmitValuesWith(received => { + const processed = received[0]; + const expected: Field[] = [ + { + name: 'Field', + type: FieldType.string, + values: new ArrayVector(['A temperature']), + config: {}, + }, + { + name: 'First', + type: FieldType.number, + values: new ArrayVector([3]), + config: {}, + }, + { + name: 'Min', + type: FieldType.number, + values: new ArrayVector([3]), + config: {}, + }, + { + name: 'Max', + type: FieldType.number, + values: new ArrayVector([6]), + config: {}, + }, + { + name: 'Last', + type: FieldType.number, + values: new ArrayVector([6]), + config: {}, + }, + ]; - expect(processed.length).toEqual(1); - expect(processed[0].length).toEqual(1); - expect(processed[0].fields).toEqual(expected); - }, - done, + expect(processed.length).toEqual(1); + expect(processed[0].length).toEqual(1); + expect(processed[0].fields).toEqual(expected); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/rename.test.ts b/packages/grafana-data/src/transformations/transformers/rename.test.ts index b4d368d1565..949586ccec4 100644 --- a/packages/grafana-data/src/transformations/transformers/rename.test.ts +++ b/packages/grafana-data/src/transformations/transformers/rename.test.ts @@ -8,7 +8,6 @@ import { } from '@grafana/data'; import { renameFieldsTransformer, RenameFieldsTransformerOptions } from './rename'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { observableTester } from '../../utils/tests/observableTester'; describe('Rename Transformer', () => { beforeAll(() => { @@ -25,62 +24,59 @@ describe('Rename Transformer', () => { ], }); - it('should rename according to config', done => { + it('should rename according to config', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.rename, options: { renameByName: { time: 'Total time', - humidity: 'Moistiness', + humidity: 'Moistness', temperature: 'how cold is it?', }, }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const renamed = data[0]; - expect(renamed.fields).toEqual([ - { - config: { - displayName: 'Total time', - }, - labels: undefined, - name: 'time', - state: { - displayName: 'Total time', - }, - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const renamed = data[0]; + expect(renamed.fields).toEqual([ + { + config: { + displayName: 'Total time', }, - { - config: { - displayName: 'how cold is it?', - }, - labels: undefined, - name: 'temperature', - state: { - displayName: 'how cold is it?', - }, - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + labels: undefined, + name: 'time', + state: { + displayName: 'Total time', }, - { - config: { - displayName: 'Moistiness', - }, - name: 'humidity', - labels: undefined, - state: { - displayName: 'Moistiness', - }, - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + }, + { + config: { + displayName: 'how cold is it?', }, - ]); - }, - done, + labels: undefined, + name: 'temperature', + state: { + displayName: 'how cold is it?', + }, + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + { + config: { + displayName: 'Moistness', + }, + name: 'humidity', + labels: undefined, + state: { + displayName: 'Moistness', + }, + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + }, + ]); }); }); }); @@ -95,7 +91,7 @@ describe('Rename Transformer', () => { ], }); - it('should not rename fields missing in config', done => { + it('should not rename fields missing in config', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.rename, options: { @@ -107,48 +103,45 @@ describe('Rename Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const renamed = data[0]; - expect(renamed.fields).toEqual([ - { - config: { - displayName: 'ttl', - }, - name: 'time', - labels: undefined, - state: { - displayName: 'ttl', - }, - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const renamed = data[0]; + expect(renamed.fields).toEqual([ + { + config: { + displayName: 'ttl', }, - { - config: {}, - labels: undefined, - name: 'pressure', - state: { - displayName: 'pressure', - }, - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + name: 'time', + labels: undefined, + state: { + displayName: 'ttl', }, - { - config: { - displayName: 'hum', - }, - labels: undefined, - name: 'humidity', - state: { - displayName: 'hum', - }, - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + }, + { + config: {}, + labels: undefined, + name: 'pressure', + state: { + displayName: 'pressure', }, - ]); - }, - done, + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + { + config: { + displayName: 'hum', + }, + labels: undefined, + name: 'humidity', + state: { + displayName: 'hum', + }, + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + }, + ]); }); }); }); @@ -163,7 +156,7 @@ describe('Rename Transformer', () => { ], }); - it('should keep the same names as in the incoming data', done => { + it('should keep the same names as in the incoming data', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.rename, options: { @@ -171,32 +164,29 @@ describe('Rename Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [data]), - expect: data => { - const renamed = data[0]; - expect(renamed.fields).toEqual([ - { - config: {}, - name: 'time', - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000]), - }, - { - config: {}, - name: 'pressure', - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), - }, - { - config: {}, - name: 'humidity', - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [data])).toEmitValuesWith(received => { + const data = received[0]; + const renamed = data[0]; + expect(renamed.fields).toEqual([ + { + config: {}, + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000]), + }, + { + config: {}, + name: 'pressure', + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6]), + }, + { + config: {}, + name: 'humidity', + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]), + }, + ]); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts index c48a14dfe87..4d0e60490e5 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts @@ -9,7 +9,6 @@ import { } from '@grafana/data'; import { SeriesToColumnsOptions, seriesToColumnsTransformer } from './seriesToColumns'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { observableTester } from '../../utils/tests/observableTester'; describe('SeriesToColumns Transformer', () => { beforeAll(() => { @@ -34,7 +33,7 @@ describe('SeriesToColumns Transformer', () => { ], }); - it('joins by time field', done => { + it('joins by time field', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -42,68 +41,65 @@ describe('SeriesToColumns Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields).toEqual([ - { - name: 'time', - state: { - displayName: 'time', - }, - type: FieldType.time, - values: new ArrayVector([1000, 3000, 4000, 5000, 6000, 7000]), - config: {}, - labels: undefined, + await expect(transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields).toEqual([ + { + name: 'time', + state: { + displayName: 'time', }, - { - name: 'temperature', - state: { - displayName: 'temperature even', - }, - type: FieldType.number, - values: new ArrayVector([null, 10.3, 10.4, 10.5, 10.6, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.time, + values: new ArrayVector([1000, 3000, 4000, 5000, 6000, 7000]), + config: {}, + labels: undefined, + }, + { + name: 'temperature', + state: { + displayName: 'temperature even', }, - { - name: 'humidity', - state: { - displayName: 'humidity even', - }, - type: FieldType.number, - values: new ArrayVector([null, 10000.3, 10000.4, 10000.5, 10000.6, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.number, + values: new ArrayVector([null, 10.3, 10.4, 10.5, 10.6, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity even', }, - { - name: 'temperature', - state: { - displayName: 'temperature odd', - }, - type: FieldType.number, - values: new ArrayVector([11.1, 11.3, null, 11.5, null, 11.7]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.number, + values: new ArrayVector([null, 10000.3, 10000.4, 10000.5, 10000.6, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'temperature', + state: { + displayName: 'temperature odd', }, - { - name: 'humidity', - state: { - displayName: 'humidity odd', - }, - type: FieldType.number, - values: new ArrayVector([11000.1, 11000.3, null, 11000.5, null, 11000.7]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.number, + values: new ArrayVector([11.1, 11.3, null, 11.5, null, 11.7]), + config: {}, + labels: { name: 'odd' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity odd', }, - ]); - }, - done, + type: FieldType.number, + values: new ArrayVector([11000.1, 11000.3, null, 11000.5, null, 11000.7]), + config: {}, + labels: { name: 'odd' }, + }, + ]); }); }); - it('joins by temperature field', done => { + it('joins by temperature field', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -111,68 +107,65 @@ describe('SeriesToColumns Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields).toEqual([ - { - name: 'temperature', - state: { - displayName: 'temperature', - }, - type: FieldType.number, - values: new ArrayVector([10.3, 10.4, 10.5, 10.6, 11.1, 11.3, 11.5, 11.7]), - config: {}, - labels: undefined, + await expect(transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields).toEqual([ + { + name: 'temperature', + state: { + displayName: 'temperature', }, - { - name: 'time', - state: { - displayName: 'time even', - }, - type: FieldType.time, - values: new ArrayVector([3000, 4000, 5000, 6000, null, null, null, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.number, + values: new ArrayVector([10.3, 10.4, 10.5, 10.6, 11.1, 11.3, 11.5, 11.7]), + config: {}, + labels: undefined, + }, + { + name: 'time', + state: { + displayName: 'time even', }, - { - name: 'humidity', - state: { - displayName: 'humidity even', - }, - type: FieldType.number, - values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6, null, null, null, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000, null, null, null, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity even', }, - { - name: 'time', - state: { - displayName: 'time odd', - }, - type: FieldType.time, - values: new ArrayVector([null, null, null, null, 1000, 3000, 5000, 7000]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.number, + values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6, null, null, null, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'time', + state: { + displayName: 'time odd', }, - { - name: 'humidity', - state: { - displayName: 'humidity odd', - }, - type: FieldType.number, - values: new ArrayVector([null, null, null, null, 11000.1, 11000.3, 11000.5, 11000.7]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.time, + values: new ArrayVector([null, null, null, null, 1000, 3000, 5000, 7000]), + config: {}, + labels: { name: 'odd' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity odd', }, - ]); - }, - done, + type: FieldType.number, + values: new ArrayVector([null, null, null, null, 11000.1, 11000.3, 11000.5, 11000.7]), + config: {}, + labels: { name: 'odd' }, + }, + ]); }); }); - it('joins by time field in reverse order', done => { + it('joins by time field in reverse order', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -184,64 +177,61 @@ describe('SeriesToColumns Transformer', () => { everySecondSeries.fields[1].values = new ArrayVector(everySecondSeries.fields[1].values.toArray().reverse()); everySecondSeries.fields[2].values = new ArrayVector(everySecondSeries.fields[2].values.toArray().reverse()); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields).toEqual([ - { - name: 'time', - state: { - displayName: 'time', - }, - type: FieldType.time, - values: new ArrayVector([1000, 3000, 4000, 5000, 6000, 7000]), - config: {}, - labels: undefined, + await expect(transformDataFrame([cfg], [everySecondSeries, everyOtherSecondSeries])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields).toEqual([ + { + name: 'time', + state: { + displayName: 'time', }, - { - name: 'temperature', - state: { - displayName: 'temperature even', - }, - type: FieldType.number, - values: new ArrayVector([null, 10.3, 10.4, 10.5, 10.6, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.time, + values: new ArrayVector([1000, 3000, 4000, 5000, 6000, 7000]), + config: {}, + labels: undefined, + }, + { + name: 'temperature', + state: { + displayName: 'temperature even', }, - { - name: 'humidity', - state: { - displayName: 'humidity even', - }, - type: FieldType.number, - values: new ArrayVector([null, 10000.3, 10000.4, 10000.5, 10000.6, null]), - config: {}, - labels: { name: 'even' }, + type: FieldType.number, + values: new ArrayVector([null, 10.3, 10.4, 10.5, 10.6, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity even', }, - { - name: 'temperature', - state: { - displayName: 'temperature odd', - }, - type: FieldType.number, - values: new ArrayVector([11.1, 11.3, null, 11.5, null, 11.7]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.number, + values: new ArrayVector([null, 10000.3, 10000.4, 10000.5, 10000.6, null]), + config: {}, + labels: { name: 'even' }, + }, + { + name: 'temperature', + state: { + displayName: 'temperature odd', }, - { - name: 'humidity', - state: { - displayName: 'humidity odd', - }, - type: FieldType.number, - values: new ArrayVector([11000.1, 11000.3, null, 11000.5, null, 11000.7]), - config: {}, - labels: { name: 'odd' }, + type: FieldType.number, + values: new ArrayVector([11.1, 11.3, null, 11.5, null, 11.7]), + config: {}, + labels: { name: 'odd' }, + }, + { + name: 'humidity', + state: { + displayName: 'humidity odd', }, - ]); - }, - done, + type: FieldType.number, + values: new ArrayVector([11000.1, 11000.3, null, 11000.5, null, 11000.7]), + config: {}, + labels: { name: 'odd' }, + }, + ]); }); }); @@ -262,7 +252,7 @@ describe('SeriesToColumns Transformer', () => { ], }); - it('when dataframe and field share the same name then use the field name', done => { + it('when dataframe and field share the same name then use the field name', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -270,9 +260,9 @@ describe('SeriesToColumns Transformer', () => { }, }; - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesWithSameFieldAndDataFrameName, seriesB]), - expect: data => { + await expect(transformDataFrame([cfg], [seriesWithSameFieldAndDataFrameName, seriesB])).toEmitValuesWith( + received => { + const data = received[0]; const filtered = data[0]; const expected: Field[] = [ { @@ -308,13 +298,12 @@ describe('SeriesToColumns Transformer', () => { ]; expect(filtered.fields).toEqual(expected); - }, - done, - }); + } + ); }); }); - it('joins if fields are missing', done => { + it('joins if fields are missing', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -343,41 +332,38 @@ describe('SeriesToColumns Transformer', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [frame1, frame2, frame3]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields).toEqual([ - { - name: 'time', - state: { displayName: 'time' }, - type: FieldType.time, - values: new ArrayVector([1, 2, 3]), - config: {}, - }, - { - name: 'temperature', - state: { displayName: 'temperature A' }, - type: FieldType.number, - values: new ArrayVector([10, 11, 12]), - config: {}, - labels: { name: 'A' }, - }, - { - name: 'temperature', - state: { displayName: 'temperature C' }, - type: FieldType.number, - values: new ArrayVector([20, 22, 24]), - config: {}, - labels: { name: 'C' }, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [frame1, frame2, frame3])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields).toEqual([ + { + name: 'time', + state: { displayName: 'time' }, + type: FieldType.time, + values: new ArrayVector([1, 2, 3]), + config: {}, + }, + { + name: 'temperature', + state: { displayName: 'temperature A' }, + type: FieldType.number, + values: new ArrayVector([10, 11, 12]), + config: {}, + labels: { name: 'A' }, + }, + { + name: 'temperature', + state: { displayName: 'temperature C' }, + type: FieldType.number, + values: new ArrayVector([20, 22, 24]), + config: {}, + labels: { name: 'C' }, + }, + ]); }); }); - it('handles duplicate field name', done => { + it('handles duplicate field name', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToColumns, options: { @@ -399,37 +385,34 @@ describe('SeriesToColumns Transformer', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [frame1, frame2]), - expect: data => { - const filtered = data[0]; - expect(filtered.fields).toEqual([ - { - name: 'time', - state: { displayName: 'time' }, - type: FieldType.time, - values: new ArrayVector([1]), - config: {}, - }, - { - name: 'temperature', - state: { displayName: 'temperature 1' }, - type: FieldType.number, - values: new ArrayVector([10]), - config: {}, - labels: {}, - }, - { - name: 'temperature', - state: { displayName: 'temperature 2' }, - type: FieldType.number, - values: new ArrayVector([20]), - config: {}, - labels: {}, - }, - ]); - }, - done, + await expect(transformDataFrame([cfg], [frame1, frame2])).toEmitValuesWith(received => { + const data = received[0]; + const filtered = data[0]; + expect(filtered.fields).toEqual([ + { + name: 'time', + state: { displayName: 'time' }, + type: FieldType.time, + values: new ArrayVector([1]), + config: {}, + }, + { + name: 'temperature', + state: { displayName: 'temperature 1' }, + type: FieldType.number, + values: new ArrayVector([10]), + config: {}, + labels: {}, + }, + { + name: 'temperature', + state: { displayName: 'temperature 2' }, + type: FieldType.number, + values: new ArrayVector([20]), + config: {}, + labels: {}, + }, + ]); }); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts index 9ef8fc74ac4..8a372dfd844 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts @@ -5,14 +5,13 @@ import { toDataFrame } from '../../dataframe'; import { transformDataFrame } from '../transformDataFrame'; import { ArrayVector } from '../../vector'; import { seriesToRowsTransformer, SeriesToRowsTransformerOptions } from './seriesToRows'; -import { observableTester } from '../../utils/tests/observableTester'; describe('Series to rows', () => { beforeAll(() => { mockTransformationsRegistry([seriesToRowsTransformer]); }); - it('combine two series into one', done => { + it('combine two series into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -34,22 +33,20 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [2000, 1000]), - createField('Metric', FieldType.string, ['B', 'A']), - createField('Value', FieldType.number, [-1, 1]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith(received => { + const result = received[0]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + const expected: Field[] = [ + createField('Time', FieldType.time, [2000, 1000]), + createField('Metric', FieldType.string, ['B', 'A']), + createField('Value', FieldType.number, [-1, 1]), + ]; + + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two series with multiple values into one', done => { + it('combine two series with multiple values into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -71,22 +68,20 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), - createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith(received => { + const result = received[0]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + const expected: Field[] = [ + createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), + createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), + createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), + ]; + + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine three series into one', done => { + it('combine three series into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -116,22 +111,20 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [seriesA, seriesB, seriesC]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [2000, 1000, 500]), - createField('Metric', FieldType.string, ['B', 'A', 'C']), - createField('Value', FieldType.number, [-1, 1, 2]), - ]; + await expect(transformDataFrame([cfg], [seriesA, seriesB, seriesC])).toEmitValuesWith(received => { + const result = received[0]; - expect(unwrap(result[0].fields)).toEqual(expected); - }, - done, + const expected: Field[] = [ + createField('Time', FieldType.time, [2000, 1000, 500]), + createField('Metric', FieldType.string, ['B', 'A', 'C']), + createField('Value', FieldType.number, [-1, 1, 2]), + ]; + + expect(unwrap(result[0].fields)).toEqual(expected); }); }); - it('combine two time series, where first serie fields has displayName, into one', done => { + it('combine two time series, where first serie fields has displayName, into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -153,25 +146,23 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), - createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; - const fields = unwrap(result[0].fields); + const expected: Field[] = [ + createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), + createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), + createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), + ]; - expect(fields[2].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + const fields = unwrap(result[0].fields); + + expect(fields[2].config).toEqual({}); + expect(fields).toEqual(expected); }); }); - it('combine two time series, where first serie fields has units, into one', done => { + it('combine two time series, where first serie fields has units, into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -193,25 +184,23 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), - createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1], { units: 'celsius' }), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; - const fields = unwrap(result[0].fields); + const expected: Field[] = [ + createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), + createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), + createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1], { units: 'celsius' }), + ]; - expect(fields[2].config).toEqual({ units: 'celsius' }); - expect(fields).toEqual(expected); - }, - done, + const fields = unwrap(result[0].fields); + + expect(fields[2].config).toEqual({ units: 'celsius' }); + expect(fields).toEqual(expected); }); }); - it('combine two time series, where second serie fields has units, into one', done => { + it('combine two time series, where second serie fields has units, into one', async () => { const cfg: DataTransformerConfig = { id: DataTransformerID.seriesToRows, options: {}, @@ -233,21 +222,19 @@ describe('Series to rows', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: transformDataFrame([cfg], [serieA, serieB]), - expect: result => { - const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), - createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), - ]; + await expect(transformDataFrame([cfg], [serieA, serieB])).toEmitValuesWith(received => { + const result = received[0]; - const fields = unwrap(result[0].fields); + const expected: Field[] = [ + createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), + createField('Metric', FieldType.string, ['A', 'A', 'B', 'B', 'A', 'B']), + createField('Value', FieldType.number, [5, 4, 3, 2, 1, -1]), + ]; - expect(fields[2].config).toEqual({}); - expect(fields).toEqual(expected); - }, - done, + const fields = unwrap(result[0].fields); + + expect(fields[2].config).toEqual({}); + expect(fields).toEqual(expected); }); }); }); diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json index ec47e7b5a9b..b2ebe333aea 100644 --- a/packages/grafana-data/tsconfig.json +++ b/packages/grafana-data/tsconfig.json @@ -7,5 +7,10 @@ }, "exclude": ["dist", "node_modules"], "extends": "@grafana/tsconfig", - "include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/sanitize-url.d.ts"] + "include": [ + "src/**/*.ts*", + "typings/jest", + "../../public/app/types/jquery/*.ts", + "../../public/app/types/sanitize-url.d.ts" + ] } diff --git a/packages/grafana-data/typings/jest/index.d.ts b/packages/grafana-data/typings/jest/index.d.ts new file mode 100644 index 00000000000..a2ba02f021a --- /dev/null +++ b/packages/grafana-data/typings/jest/index.d.ts @@ -0,0 +1,12 @@ +import { Observable } from 'rxjs'; + +type ObservableType = T extends Observable ? V : never; + +declare global { + namespace jest { + interface Matchers { + toEmitValues>(expected: E[]): Promise; + toEmitValuesWith>(expectations: (received: E[]) => void): Promise; + } + } +} diff --git a/public/app/features/annotations/standardAnnotationSupport.test.ts b/public/app/features/annotations/standardAnnotationSupport.test.ts index 6a50f5ceadc..4be61ab3184 100644 --- a/public/app/features/annotations/standardAnnotationSupport.test.ts +++ b/public/app/features/annotations/standardAnnotationSupport.test.ts @@ -1,8 +1,8 @@ -import { FieldType, observableTester, toDataFrame } from '@grafana/data'; +import { FieldType, toDataFrame } from '@grafana/data'; import { getAnnotationsFromData } from './standardAnnotationSupport'; describe('DataFrame to annotations', () => { - test('simple conversion', done => { + test('simple conversion', async () => { const frame = toDataFrame({ fields: [ { type: FieldType.time, values: [1, 2, 3, 4, 5] }, @@ -11,48 +11,44 @@ describe('DataFrame to annotations', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: getAnnotationsFromData([frame]), - expect: events => { - expect(events).toEqual([ - { - color: 'red', - tags: ['aaa', 'bbb'], - text: 't1', - time: 1, - type: 'default', - }, - { - color: 'red', - tags: ['bbb', 'ccc'], - text: 't2', - time: 2, - type: 'default', - }, - { - color: 'red', - tags: ['zyz'], - text: 't3', - time: 3, - type: 'default', - }, - { - color: 'red', - time: 4, - type: 'default', - }, - { - color: 'red', - time: 5, - type: 'default', - }, - ]); - }, - done, - }); + await expect(getAnnotationsFromData([frame])).toEmitValues([ + [ + { + color: 'red', + tags: ['aaa', 'bbb'], + text: 't1', + time: 1, + type: 'default', + }, + { + color: 'red', + tags: ['bbb', 'ccc'], + text: 't2', + time: 2, + type: 'default', + }, + { + color: 'red', + tags: ['zyz'], + text: 't3', + time: 3, + type: 'default', + }, + { + color: 'red', + time: 4, + type: 'default', + }, + { + color: 'red', + time: 5, + type: 'default', + }, + ], + ]); }); - test('explicit mappins', done => { + test('explicit mappins', async () => { const frame = toDataFrame({ fields: [ { name: 'time1', values: [111, 222, 333] }, @@ -62,42 +58,40 @@ describe('DataFrame to annotations', () => { ], }); - observableTester().subscribeAndExpectOnNext({ - observable: getAnnotationsFromData([frame], { - text: { value: 'bbbbb' }, - time: { value: 'time2' }, - timeEnd: { value: 'time1' }, - title: { value: 'aaaaa' }, - }), - expect: events => { - expect(events).toEqual([ - { - color: 'red', - text: 'b1', - time: 100, - timeEnd: 111, - title: 'a1', - type: 'default', - }, - { - color: 'red', - text: 'b2', - time: 200, - timeEnd: 222, - title: 'a2', - type: 'default', - }, - { - color: 'red', - text: 'b3', - time: 300, - timeEnd: 333, - title: 'a3', - type: 'default', - }, - ]); - }, - done, + const observable = getAnnotationsFromData([frame], { + text: { value: 'bbbbb' }, + time: { value: 'time2' }, + timeEnd: { value: 'time1' }, + title: { value: 'aaaaa' }, }); + + await expect(observable).toEmitValues([ + [ + { + color: 'red', + text: 'b1', + time: 100, + timeEnd: 111, + title: 'a1', + type: 'default', + }, + { + color: 'red', + text: 'b2', + time: 200, + timeEnd: 222, + title: 'a2', + type: 'default', + }, + { + color: 'red', + text: 'b3', + time: 300, + timeEnd: 333, + title: 'a3', + type: 'default', + }, + ], + ]); }); }); diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 6539c8a9803..80abc8d10c6 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,16 +1,7 @@ -import { of, Subject } from 'rxjs'; -import { first, last, take } from 'rxjs/operators'; +import { of, throwError } from 'rxjs'; +import { take } from 'rxjs/operators'; import { omit } from 'lodash'; -import { - AnnotationQueryRequest, - CoreApp, - DataFrame, - DataQueryResponse, - dateTime, - FieldCache, - observableTester, - TimeRange, -} from '@grafana/data'; +import { AnnotationQueryRequest, CoreApp, DataFrame, dateTime, FieldCache, TimeRange } from '@grafana/data'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; import LokiDatasource from './datasource'; @@ -20,7 +11,6 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; import { backendSrv } from 'app/core/services/backend_srv'; import { CustomVariableModel } from '../../../features/variables/types'; import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; -import { expect } from '../../../../test/lib/common'; import { makeMockLokiDatasource } from './mocks'; jest.mock('@grafana/runtime', () => ({ @@ -60,13 +50,11 @@ const testResponse: FetchResponse = { }; describe('LokiDatasource', () => { - let fetchStream: Subject; const fetchMock = jest.spyOn(backendSrv, 'fetch'); beforeEach(() => { jest.clearAllMocks(); - fetchStream = new Subject(); - fetchMock.mockImplementation(() => fetchStream.asObservable()); + fetchMock.mockImplementation(() => of(createFetchResponse({}))); }); describe('when creating range query', () => { @@ -109,12 +97,11 @@ describe('LokiDatasource', () => { }); describe('when querying with limits', () => { - const runLimitTest = ({ + const runLimitTest = async ({ maxDataPoints = 123, queryMaxLines, dsMaxLines = 456, expectedLimit, - done, expr = '{label="val"}', }: any) => { let settings: any = { @@ -134,46 +121,32 @@ describe('LokiDatasource', () => { const options = getQueryOptions({ targets: [{ expr, refId: 'B', maxLines: queryMaxLines }] }); options.maxDataPoints = maxDataPoints; - observableTester().subscribeAndExpectOnComplete({ - observable: ds.query(options).pipe(take(1)), - expect: () => { - expect(fetchMock.mock.calls.length).toBe(1); - expect(fetchMock.mock.calls[0][0].url).toContain(`limit=${expectedLimit}`); - }, - done, - }); + fetchMock.mockImplementation(() => of(testResponse)); - fetchStream.next(testResponse); + await expect(ds.query(options).pipe(take(1))).toEmitValuesWith(() => { + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0].url).toContain(`limit=${expectedLimit}`); + }); }; - it('should use datasource max lines when no limit given and it is log query', done => { - runLimitTest({ - expectedLimit: 456, - done, - }); + it('should use datasource max lines when no limit given and it is log query', async () => { + await runLimitTest({ expectedLimit: 456 }); }); - it('should use custom max lines from query if set and it is logs query', done => { - runLimitTest({ - queryMaxLines: 20, - expectedLimit: 20, - done, - }); + it('should use custom max lines from query if set and it is logs query', async () => { + await runLimitTest({ queryMaxLines: 20, expectedLimit: 20 }); }); - it('should use custom max lines from query if set and it is logs query even if it is higher than data source limit', done => { - runLimitTest({ - queryMaxLines: 500, - expectedLimit: 500, - done, - }); + it('should use custom max lines from query if set and it is logs query even if it is higher than data source limit', async () => { + await runLimitTest({ queryMaxLines: 500, expectedLimit: 500 }); }); - it('should use maxDataPoints if it is metrics query', () => { - runLimitTest({ - expr: 'rate({label="val"}[10m])', - expectedLimit: 123, - }); + it('should use maxDataPoints if it is metrics query', async () => { + await runLimitTest({ expr: 'rate({label="val"}[10m])', expectedLimit: 123 }); + }); + + it('should use maxDataPoints if it is metrics query and using search', async () => { + await runLimitTest({ expr: 'rate({label="val"}[10m])', expectedLimit: 123 }); }); }); @@ -210,84 +183,76 @@ describe('LokiDatasource', () => { expect(ds.runRangeQuery).toBeCalled(); }); - it('should return series data for both queries in Explore if metrics query', done => { + it('should return series data for both queries in Explore if metrics query', async () => { const ds = createLokiDSForTests(); const options = getQueryOptions({ targets: [{ expr: 'rate({job="grafana"} |= "foo" [10m])', refId: 'B' }], app: CoreApp.Explore, }); - observableTester().subscribeAndExpectOnNext({ - observable: ds.query(options).pipe(first()), // first result always comes from runInstantQuery - expect: res => { - expect(res).toEqual({ - data: [], - key: 'B_instant', - }); - }, - done, - }); + fetchMock + .mockImplementationOnce(() => of(testResponse)) + .mockImplementation(() => of(omit(testResponse, 'data.status'))); - observableTester().subscribeAndExpectOnNext({ - observable: ds.query(options).pipe(last()), // last result always comes from runRangeQuery - expect: res => { - const dataFrame = res.data[0] as DataFrame; - const fieldCache = new FieldCache(dataFrame); - expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello'); - expect(dataFrame.meta?.limit).toBe(20); - expect(dataFrame.meta?.searchWords).toEqual(['foo']); - }, - done, - }); + await expect(ds.query(options)).toEmitValuesWith(received => { + // first result always comes from runInstantQuery + const firstResult = received[0]; + expect(firstResult).toEqual({ data: [], key: 'B_instant' }); - fetchStream.next(testResponse); - fetchStream.next(omit(testResponse, 'data.status')); + // second result always comes from runRangeQuery + const secondResult = received[1]; + const dataFrame = secondResult.data[0] as DataFrame; + const fieldCache = new FieldCache(dataFrame); + + expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello'); + expect(dataFrame.meta?.limit).toBe(500); + expect(dataFrame.meta?.searchWords).toEqual([]); + }); }); - it('should return series data for range query in Dashboard', done => { + it('should return series data for range query in Dashboard', async () => { const ds = createLokiDSForTests(); const options = getQueryOptions({ targets: [{ expr: '{job="grafana"} |= "foo"', refId: 'B' }], }); - observableTester().subscribeAndExpectOnNext({ - observable: ds.query(options).pipe(first()), // first result will come from runRangeQuery - expect: res => { - const dataFrame = res.data[0] as DataFrame; - const fieldCache = new FieldCache(dataFrame); - expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello'); - expect(dataFrame.meta?.limit).toBe(20); - expect(dataFrame.meta?.searchWords).toEqual(['foo']); - }, - done, - }); + fetchMock + .mockImplementationOnce(() => of(testResponse)) + .mockImplementation(() => of(omit(testResponse, 'data.status'))); - fetchStream.next(testResponse); - fetchStream.next(omit(testResponse, 'data.status')); + await expect(ds.query(options)).toEmitValuesWith(received => { + // first result will come from runRangeQuery + const firstResult = received[0]; + const dataFrame = firstResult.data[0] as DataFrame; + const fieldCache = new FieldCache(dataFrame); + + expect(fieldCache.getFieldByName('line')?.values.get(0)).toBe('hello'); + expect(dataFrame.meta?.limit).toBe(20); + expect(dataFrame.meta?.searchWords).toEqual(['foo']); + }); }); - it('should return custom error message when Loki returns escaping error', done => { + it('should return custom error message when Loki returns escaping error', async () => { const ds = createLokiDSForTests(); const options = getQueryOptions({ targets: [{ expr: '{job="gra\\fana"}', refId: 'B' }], }); - observableTester().subscribeAndExpectOnError({ - observable: ds.query(options), - expect: err => { - expect(err.data.message).toBe( - 'Error: parse error at line 1, col 6: invalid char escape. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.' - ); - }, - done, - }); + fetchMock.mockImplementation(() => + throwError({ + data: { + message: 'parse error at line 1, col 6: invalid char escape', + }, + status: 400, + statusText: 'Bad Request', + }) + ); - fetchStream.error({ - data: { - message: 'parse error at line 1, col 6: invalid char escape', - }, - status: 400, - statusText: 'Bad Request', + await expect(ds.query(options)).toEmitValuesWith(received => { + const err: any = received[0]; + expect(err.data.message).toBe( + 'Error: parse error at line 1, col 6: invalid char escape. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.' + ); }); }); }); @@ -339,27 +304,12 @@ describe('LokiDatasource', () => { }); describe('when performing testDataSource', () => { - const getTestContext = () => { - const ds = createLokiDSForTests({} as TemplateSrv); - const promise = ds.testDatasource(); - - return { promise }; - }; - describe('and call succeeds', () => { it('should return successfully', async () => { - const { promise } = getTestContext(); + fetchMock.mockImplementation(() => of(createFetchResponse({ values: ['avalue'] }))); + const ds = createLokiDSForTests({} as TemplateSrv); - fetchStream.next(({ - status: 200, - data: { - values: ['avalue'], - }, - } as unknown) as FetchResponse); - - fetchStream.complete(); - - const result = await promise; + const result = await ds.testDatasource(); expect(result.status).toBe('success'); }); @@ -367,17 +317,18 @@ describe('LokiDatasource', () => { describe('and call fails with 401 error', () => { it('should return error status and a detailed error message', async () => { - const { promise } = getTestContext(); + fetchMock.mockImplementation(() => + throwError({ + statusText: 'Unauthorized', + status: 401, + data: { + message: 'Unauthorized', + }, + }) + ); + const ds = createLokiDSForTests({} as TemplateSrv); - fetchStream.error({ - statusText: 'Unauthorized', - status: 401, - data: { - message: 'Unauthorized', - }, - }); - - const result = await promise; + const result = await ds.testDatasource(); expect(result.status).toEqual('error'); expect(result.message).toBe('Loki: Unauthorized. 401. Unauthorized'); @@ -386,17 +337,19 @@ describe('LokiDatasource', () => { describe('and call fails with 404 error', () => { it('should return error status and a detailed error message', async () => { - const { promise } = getTestContext(); + fetchMock.mockImplementation(() => + throwError({ + statusText: 'Not found', + status: 404, + data: { + message: '404 page not found', + }, + }) + ); - fetchStream.error({ - statusText: 'Not found', - status: 404, - data: { - message: '404 page not found', - }, - }); + const ds = createLokiDSForTests({} as TemplateSrv); - const result = await promise; + const result = await ds.testDatasource(); expect(result.status).toEqual('error'); expect(result.message).toBe('Loki: Not found. 404. 404 page not found'); @@ -405,15 +358,17 @@ describe('LokiDatasource', () => { describe('and call fails with 502 error', () => { it('should return error status and a detailed error message', async () => { - const { promise } = getTestContext(); + fetchMock.mockImplementation(() => + throwError({ + statusText: 'Bad Gateway', + status: 502, + data: '', + }) + ); - fetchStream.error({ - statusText: 'Bad Gateway', - status: 502, - data: '', - }); + const ds = createLokiDSForTests({} as TemplateSrv); - const result = await promise; + const result = await ds.testDatasource(); expect(result.status).toEqual('error'); expect(result.message).toBe('Loki: Bad Gateway. 502'); @@ -440,8 +395,10 @@ describe('LokiDatasource', () => { }); describe('when calling annotationQuery', () => { - const getTestContext = () => { + const getTestContext = (response: any) => { const query = makeAnnotationQueryRequest(); + fetchMock.mockImplementation(() => of(response)); + const ds = createLokiDSForTests(); const promise = ds.annotationQuery(query); @@ -449,7 +406,6 @@ describe('LokiDatasource', () => { }; it('should transform the loki data to annotation response', async () => { - const { promise } = getTestContext(); const response: FetchResponse = ({ data: { data: { @@ -473,9 +429,7 @@ describe('LokiDatasource', () => { status: 'success', }, } as unknown) as FetchResponse; - - fetchStream.next(response); - fetchStream.complete(); + const { promise } = getTestContext(response); const res = await promise; @@ -590,3 +544,17 @@ function makeMetadataAndVersionsMocks() { } return mocks; } + +function createFetchResponse(data: T): FetchResponse { + return { + data, + status: 200, + url: 'http://localhost:3000/api/query', + config: { url: 'http://localhost:3000/api/query' }, + type: 'basic', + statusText: 'Ok', + redirected: false, + headers: ({} as unknown) as Headers, + ok: true, + }; +} diff --git a/public/app/plugins/datasource/postgres/specs/datasource.test.ts b/public/app/plugins/datasource/postgres/specs/datasource.test.ts index c26e8fd2239..7f67c6315b4 100644 --- a/public/app/plugins/datasource/postgres/specs/datasource.test.ts +++ b/public/app/plugins/datasource/postgres/specs/datasource.test.ts @@ -1,7 +1,7 @@ import { of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { FetchResponse } from '@grafana/runtime'; -import { dateTime, observableTester, toUtc } from '@grafana/data'; +import { dateTime, toUtc } from '@grafana/data'; import { PostgresDatasource } from '../datasource'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ @@ -207,7 +207,7 @@ describe('PostgreSQLDatasource', () => { }); describe('When performing a query with hidden target', () => { - it('should return empty result and backendSrv.fetch should not be called', done => { + it('should return empty result and backendSrv.fetch should not be called', async () => { const options = { range: { from: dateTime(1432288354), @@ -227,15 +227,9 @@ describe('PostgreSQLDatasource', () => { const { ds } = setupTestContext({}); - observableTester().subscribeAndExpectOnNextAndComplete({ - observable: ds.query(options), - expectOnNext: value => { - expect(value).toEqual({ data: [] }); - }, - expectOnComplete: () => { - expect(fetchMock).not.toHaveBeenCalled(); - }, - done, + await expect(ds.query(options)).toEmitValuesWith(received => { + expect(received[0]).toEqual({ data: [] }); + expect(fetchMock).not.toHaveBeenCalled(); }); }); }); diff --git a/public/test/matchers/index.ts b/public/test/matchers/index.ts new file mode 100644 index 00000000000..3b955e37493 --- /dev/null +++ b/public/test/matchers/index.ts @@ -0,0 +1,9 @@ +import { ObservableMatchers } from './types'; +import { toEmitValues } from './toEmitValues'; +import { toEmitValuesWith } from './toEmitValuesWith'; +import { Observable } from 'rxjs'; + +export const matchers: ObservableMatchers> = { + toEmitValues, + toEmitValuesWith, +}; diff --git a/public/test/matchers/toEmitValues.test.ts b/public/test/matchers/toEmitValues.test.ts new file mode 100644 index 00000000000..5106353f686 --- /dev/null +++ b/public/test/matchers/toEmitValues.test.ts @@ -0,0 +1,139 @@ +import { interval, Observable, of, throwError } from 'rxjs'; +import { map, mergeMap, take } from 'rxjs/operators'; +import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; + +describe('toEmitValues matcher', () => { + describe('failing tests', () => { + describe('passing null in expect', () => { + it('should fail', async () => { + const observable = (null as unknown) as Observable; + + const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; + await rejects.toThrow(); + }); + }); + + describe('passing undefined in expect', () => { + it('should fail', async () => { + const observable = (undefined as unknown) as Observable; + + const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; + await rejects.toThrow(); + }); + }); + + describe('passing number instead of Observable in expect', () => { + it('should fail', async () => { + const observable = (1 as unknown) as Observable; + + const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; + await rejects.toThrow(); + }); + }); + + describe('wrong number of emitted values', () => { + it('should fail', async () => { + const observable = interval(10).pipe(take(3)); + + const rejects = expect(() => expect(observable).toEmitValues([0, 1])).rejects; + await rejects.toThrow(); + }); + }); + + describe('wrong emitted values', () => { + it('should fail', async () => { + const observable = interval(10).pipe(take(3)); + + const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; + await rejects.toThrow(); + }); + }); + + describe('wrong emitted value types', () => { + it('should fail', async () => { + const observable = (interval(10).pipe(take(3)) as unknown) as Observable; + + const rejects = expect(() => expect(observable).toEmitValues(['0', '1', '2'])).rejects; + await rejects.toThrow(); + }); + }); + + describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => { + it('should fail', async () => { + const observable = interval(600); + + const rejects = expect(() => expect(observable).toEmitValues([0])).rejects; + await rejects.toThrow(); + }); + }); + }); + + describe('passing tests', () => { + describe('correct emitted values', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe(take(3)); + await expect(observable).toEmitValues([0, 1, 2]); + }); + }); + + describe('using nested arrays', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe( + map(interval => [{ text: interval.toString(), value: interval }]), + take(3) + ); + await expect(observable).toEmitValues([ + [{ text: '0', value: 0 }], + [{ text: '1', value: 1 }], + [{ text: '2', value: 2 }], + ]); + }); + }); + + describe('using nested objects', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe( + map(interval => ({ inner: { text: interval.toString(), value: interval } })), + take(3) + ); + await expect(observable).toEmitValues([ + { inner: { text: '0', value: 0 } }, + { inner: { text: '1', value: 1 } }, + { inner: { text: '2', value: 2 } }, + ]); + }); + }); + + describe('correct emitted values with throw', () => { + it('should pass with correct message', async () => { + const observable = (interval(10).pipe( + map(interval => { + if (interval > 1) { + throw 'an error'; + } + + return interval; + }) + ) as unknown) as Observable; + + await expect(observable).toEmitValues([0, 1, 'an error']); + }); + }); + + describe('correct emitted values with throwError', () => { + it('should pass with correct message', async () => { + const observable = (interval(10).pipe( + mergeMap(interval => { + if (interval === 1) { + return throwError('an error'); + } + + return of(interval); + }) + ) as unknown) as Observable; + + await expect(observable).toEmitValues([0, 'an error']); + }); + }); + }); +}); diff --git a/public/test/matchers/toEmitValues.ts b/public/test/matchers/toEmitValues.ts new file mode 100644 index 00000000000..a55b02f1a21 --- /dev/null +++ b/public/test/matchers/toEmitValues.ts @@ -0,0 +1,88 @@ +import { Observable, Subscription } from 'rxjs'; +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; +import { expectObservable, forceObservableCompletion } from './utils'; +import isEqual from 'lodash/isEqual'; + +function passMessage(received: any[], expected: any[]) { + return `${matcherHint('.not.toEmitValues')} + + Expected observable to emit values: + ${printExpected(expected)} + Received: + ${printReceived(received)} + `; +} + +function failMessage(received: any[], expected: any[]) { + return `${matcherHint('.toEmitValues')} + + Expected observable to emit values: + ${printExpected(expected)} + Received: + ${printReceived(received)} + `; +} + +function tryExpectations(received: any[], expected: any[]): jest.CustomMatcherResult { + try { + if (received.length !== expected.length) { + return { + pass: false, + message: () => failMessage(received, expected), + }; + } + + for (let index = 0; index < received.length; index++) { + const left = received[index]; + const right = expected[index]; + + if (!isEqual(left, right)) { + return { + pass: false, + message: () => failMessage(received, expected), + }; + } + } + + return { + pass: true, + message: () => passMessage(received, expected), + }; + } catch (err) { + return { + pass: false, + message: () => err, + }; + } +} + +export function toEmitValues(received: Observable, expected: any[]): Promise { + const failsChecks = expectObservable(received); + if (failsChecks) { + return Promise.resolve(failsChecks); + } + + return new Promise(resolve => { + const receivedValues: any[] = []; + const subscription = new Subscription(); + + subscription.add( + received.subscribe({ + next: value => { + receivedValues.push(value); + }, + error: err => { + receivedValues.push(err); + subscription.unsubscribe(); + resolve(tryExpectations(receivedValues, expected)); + }, + complete: () => { + subscription.unsubscribe(); + resolve(tryExpectations(receivedValues, expected)); + }, + }) + ); + + forceObservableCompletion(subscription, resolve); + }); +} diff --git a/public/test/matchers/toEmitValuesWith.test.ts b/public/test/matchers/toEmitValuesWith.test.ts new file mode 100644 index 00000000000..d229c203eba --- /dev/null +++ b/public/test/matchers/toEmitValuesWith.test.ts @@ -0,0 +1,153 @@ +import { interval, Observable, of, throwError } from 'rxjs'; +import { map, mergeMap, take } from 'rxjs/operators'; + +import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; + +describe('toEmitValuesWith matcher', () => { + describe('failing tests', () => { + describe('passing null in expect', () => { + it('should fail with correct message', async () => { + const observable = (null as unknown) as Observable; + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([1, 2, 3]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe('passing undefined in expect', () => { + it('should fail with correct message', async () => { + const observable = (undefined as unknown) as Observable; + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([1, 2, 3]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe('passing number instead of Observable in expect', () => { + it('should fail with correct message', async () => { + const observable = (1 as unknown) as Observable; + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([1, 2, 3]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe('wrong number of emitted values', () => { + it('should fail with correct message', async () => { + const observable = interval(10).pipe(take(3)); + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([0, 1]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe('wrong emitted values', () => { + it('should fail with correct message', async () => { + const observable = interval(10).pipe(take(3)); + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([1, 2, 3]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe('wrong emitted value types', () => { + it('should fail with correct message', async () => { + const observable = (interval(10).pipe(take(3)) as unknown) as Observable; + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual(['0', '1', '2']); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + + describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => { + it('should fail with correct message', async () => { + const observable = interval(600); + + const rejects = expect(() => + expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([0]); + }) + ).rejects; + + await rejects.toThrow(); + }); + }); + }); + + describe('passing tests', () => { + describe('correct emitted values', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe(take(3)); + await expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([0, 1, 2]); + }); + }); + }); + + describe('correct emitted values with throw', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe( + map(interval => { + if (interval > 1) { + throw 'an error'; + } + + return interval; + }) + ); + + await expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([0, 1, 'an error']); + }); + }); + }); + + describe('correct emitted values with throwError', () => { + it('should pass with correct message', async () => { + const observable = interval(10).pipe( + mergeMap(interval => { + if (interval === 1) { + return throwError('an error'); + } + + return of(interval); + }) + ); + + await expect(observable).toEmitValuesWith(received => { + expect(received).toEqual([0, 'an error']); + }); + }); + }); + }); +}); diff --git a/public/test/matchers/toEmitValuesWith.ts b/public/test/matchers/toEmitValuesWith.ts new file mode 100644 index 00000000000..f3bbd4a95ed --- /dev/null +++ b/public/test/matchers/toEmitValuesWith.ts @@ -0,0 +1,56 @@ +import { Observable, Subscription } from 'rxjs'; +import { expectObservable, forceObservableCompletion } from './utils'; +import { matcherHint, printReceived } from 'jest-matcher-utils'; + +function tryExpectations(received: any[], expectations: (received: any[]) => void): jest.CustomMatcherResult { + try { + expectations(received); + return { + pass: true, + message: () => `${matcherHint('.not.toEmitValues')} + + Expected observable to complete with + ${printReceived(received)} + `, + }; + } catch (err) { + return { + pass: false, + message: () => 'failed ' + err, + }; + } +} + +export function toEmitValuesWith( + received: Observable, + expectations: (actual: any[]) => void +): Promise { + const failsChecks = expectObservable(received); + if (failsChecks) { + return Promise.resolve(failsChecks); + } + + return new Promise(resolve => { + const receivedValues: any[] = []; + const subscription = new Subscription(); + + subscription.add( + received.subscribe({ + next: value => { + receivedValues.push(value); + }, + error: err => { + receivedValues.push(err); + subscription.unsubscribe(); + resolve(tryExpectations(receivedValues, expectations)); + }, + complete: () => { + subscription.unsubscribe(); + resolve(tryExpectations(receivedValues, expectations)); + }, + }) + ); + + forceObservableCompletion(subscription, resolve); + }); +} diff --git a/public/test/matchers/types.ts b/public/test/matchers/types.ts new file mode 100644 index 00000000000..c67b32897f5 --- /dev/null +++ b/public/test/matchers/types.ts @@ -0,0 +1,13 @@ +import { Observable } from 'rxjs'; + +export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000; + +export type ObservableType = T extends Observable ? V : never; + +export interface ObservableMatchers extends jest.ExpectExtendMap { + toEmitValues>(received: T, expected: E[]): Promise; + toEmitValuesWith>( + received: T, + expectations: (received: E[]) => void + ): Promise; +} diff --git a/public/test/matchers/utils.ts b/public/test/matchers/utils.ts new file mode 100644 index 00000000000..a6a95802f8d --- /dev/null +++ b/public/test/matchers/utils.ts @@ -0,0 +1,62 @@ +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; +import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; +import { asapScheduler, Observable, Subscription, timer } from 'rxjs'; + +export function forceObservableCompletion(subscription: Subscription, resolve: (args: any) => void) { + const timeoutObservable = timer(OBSERVABLE_TEST_TIMEOUT_IN_MS, asapScheduler); + + subscription.add( + timeoutObservable.subscribe(() => { + subscription.unsubscribe(); + resolve({ + pass: false, + message: () => + `${matcherHint('.toEmitValues')} + + Expected ${printReceived('Observable')} to be ${printExpected( + `completed within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms` + )} but it did not.`, + }); + }) + ); +} + +export function expectObservableToBeDefined(received: any): jest.CustomMatcherResult | null { + if (received) { + return null; + } + + return { + pass: false, + message: () => `${matcherHint('.toEmitValues')} + +Expected ${printReceived(received)} to be ${printExpected('defined')}.`, + }; +} + +export function expectObservableToBeObservable(received: any): jest.CustomMatcherResult | null { + if (received instanceof Observable) { + return null; + } + + return { + pass: false, + message: () => `${matcherHint('.toEmitValues')} + +Expected ${printReceived(received)} to be ${printExpected('an Observable')}.`, + }; +} + +export function expectObservable(received: any): jest.CustomMatcherResult | null { + const toBeDefined = expectObservableToBeDefined(received); + if (toBeDefined) { + return toBeDefined; + } + + const toBeObservable = expectObservableToBeObservable(received); + if (toBeObservable) { + return toBeObservable; + } + + return null; +} diff --git a/public/test/setupTests.ts b/public/test/setupTests.ts index 7b0828bfa80..3fcfc535ef5 100644 --- a/public/test/setupTests.ts +++ b/public/test/setupTests.ts @@ -1 +1,4 @@ import '@testing-library/jest-dom'; +import { matchers } from './matchers'; + +expect.extend(matchers); diff --git a/tsconfig.json b/tsconfig.json index 4985162ee5b..fc876b4b468 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "public/e2e-test/**/*.ts", "public/test/**/*.ts", "public/vendor/**/*.ts", - "packages/jaeger-ui-components/typings" + "packages/jaeger-ui-components/typings", + "packages/grafana-data/typings" ] } diff --git a/yarn.lock b/yarn.lock index d457e8dc90d..70e74bec1b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16693,7 +16693,7 @@ jest-get-type@^25.2.6: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== -jest-get-type@^26.3.0: +jest-get-type@^26.0.0, jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== @@ -16831,6 +16831,16 @@ jest-leak-detector@^26.5.2: jest-get-type "^26.3.0" pretty-format "^26.5.2" +jest-matcher-utils@26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.0.0.tgz#1b288fee2362639a61975ce52b003034a6c2b39e" + integrity sha512-ektfkGT4pByMWc6NaCzHGCVZWl3XR1nNizVP1jVsGs/Qlh9iDunITrJxSTd8xK9/XgWA3oJ13orpUt82JIc2UA== + dependencies: + chalk "^4.0.0" + jest-diff "^26.0.0" + jest-get-type "^26.0.0" + pretty-format "^26.0.0" + jest-matcher-utils@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"