mirror of https://github.com/grafana/grafana
Chore: Cleanup duplicated code in grafana-prometheus package (#89542)
* remove redundant test matchers * use amendTable, trimTable functions from @grafana/data package * move getMockDataSource function into the mocks.ts * use LocalStorageValueProvider from @grafana/o11y-ds-frontend * move all mocks under __mocks__ directory * use store from @grafana/o11y-ds-frontend * move test related files under test directory * use getNextRefId from @grafana/data instead of deprecated getNextRefIdChar See: https://github.com/grafana/grafana/pull/87460 * betterer * remove unnecessary mockings * import from @grafana/data * import from @grafana/datapull/89952/head^2
parent
f659bc1f40
commit
7f5dde6ed3
@ -1,15 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/mocks.ts
|
||||
import { DataSourceSettings } from '@grafana/data'; |
||||
|
||||
import { getMockDataSource } from '../gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks'; |
||||
import { PromOptions } from '../types'; |
||||
|
||||
export function createDefaultConfigOptions(): DataSourceSettings<PromOptions> { |
||||
return getMockDataSource<PromOptions>({ |
||||
jsonData: { |
||||
timeInterval: '1m', |
||||
queryTimeout: '1m', |
||||
httpMethod: 'GET', |
||||
}, |
||||
}); |
||||
} |
@ -1,51 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx
|
||||
import { useEffect, useState } from 'react'; |
||||
import * as React from 'react'; |
||||
|
||||
import store from '../../store'; |
||||
|
||||
export interface Props<T> { |
||||
storageKey: string; |
||||
defaultValue: T; |
||||
children: (value: T, onSaveToStore: (value: T) => void, onDeleteFromStore: () => void) => React.ReactNode; |
||||
} |
||||
|
||||
export const LocalStorageValueProvider = <T,>(props: Props<T>) => { |
||||
const { children, storageKey, defaultValue } = props; |
||||
|
||||
const [state, setState] = useState({ value: store.getObject(props.storageKey, props.defaultValue) }); |
||||
|
||||
useEffect(() => { |
||||
const onStorageUpdate = (v: StorageEvent) => { |
||||
if (v.key === storageKey) { |
||||
setState({ value: store.getObject(props.storageKey, props.defaultValue) }); |
||||
} |
||||
}; |
||||
|
||||
window.addEventListener('storage', onStorageUpdate); |
||||
|
||||
return () => { |
||||
window.removeEventListener('storage', onStorageUpdate); |
||||
}; |
||||
}); |
||||
|
||||
const onSaveToStore = (value: T) => { |
||||
try { |
||||
store.setObject(storageKey, value); |
||||
} catch (error) { |
||||
console.error(error); |
||||
} |
||||
setState({ value }); |
||||
}; |
||||
|
||||
const onDeleteFromStore = () => { |
||||
try { |
||||
store.delete(storageKey); |
||||
} catch (error) { |
||||
console.log(error); |
||||
} |
||||
setState({ value: defaultValue }); |
||||
}; |
||||
|
||||
return <>{children(state.value, onSaveToStore, onDeleteFromStore)}</>; |
||||
}; |
@ -1,2 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/index.tsx
|
||||
export { LocalStorageValueProvider } from './LocalStorageValueProvider'; |
@ -1,66 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/store.ts
|
||||
type StoreValue = string | number | boolean | null; |
||||
|
||||
export class Store { |
||||
get(key: string) { |
||||
return window.localStorage[key]; |
||||
} |
||||
|
||||
set(key: string, value: StoreValue) { |
||||
window.localStorage[key] = value; |
||||
} |
||||
|
||||
getBool(key: string, def: boolean): boolean { |
||||
if (def !== void 0 && !this.exists(key)) { |
||||
return def; |
||||
} |
||||
return window.localStorage[key] === 'true'; |
||||
} |
||||
|
||||
getObject<T = unknown>(key: string): T | undefined; |
||||
getObject<T = unknown>(key: string, def: T): T; |
||||
getObject<T = unknown>(key: string, def?: T) { |
||||
let ret = def; |
||||
if (this.exists(key)) { |
||||
const json = window.localStorage[key]; |
||||
try { |
||||
ret = JSON.parse(json); |
||||
} catch (error) { |
||||
console.error(`Error parsing store object: ${key}. Returning default: ${def}. [${error}]`); |
||||
} |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
/* Returns true when successfully stored, throws error if not successfully stored */ |
||||
setObject(key: string, value: unknown) { |
||||
let json; |
||||
try { |
||||
json = JSON.stringify(value); |
||||
} catch (error) { |
||||
throw new Error(`Could not stringify object: ${key}. [${error}]`); |
||||
} |
||||
try { |
||||
this.set(key, json); |
||||
} catch (error) { |
||||
// Likely hitting storage quota
|
||||
const errorToThrow = new Error(`Could not save item in localStorage: ${key}. [${error}]`); |
||||
if (error instanceof Error) { |
||||
errorToThrow.name = error.name; |
||||
} |
||||
throw errorToThrow; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
exists(key: string) { |
||||
return window.localStorage[key] !== void 0; |
||||
} |
||||
|
||||
delete(key: string) { |
||||
window.localStorage.removeItem(key); |
||||
} |
||||
} |
||||
|
||||
const store = new Store(); |
||||
export default store; |
@ -1,21 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/utils/query.ts
|
||||
import { DataQuery } from '@grafana/data'; |
||||
|
||||
export const getNextRefIdChar = (queries: DataQuery[]): string => { |
||||
for (let num = 0; ; num++) { |
||||
const refId = getRefId(num); |
||||
if (!queries.some((query) => query.refId === refId)) { |
||||
return refId; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
function getRefId(num: number): string { |
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
||||
if (num < letters.length) { |
||||
return letters[num]; |
||||
} else { |
||||
return getRefId(Math.floor(num / letters.length) - 1) + letters[num % letters.length]; |
||||
} |
||||
} |
@ -1,31 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/datasources/__mocks__/dataSourcesMocks.ts
|
||||
import { merge } from 'lodash'; |
||||
|
||||
import { DataSourceJsonData, DataSourceSettings } from '@grafana/data'; |
||||
|
||||
export const getMockDataSource = <T extends DataSourceJsonData>( |
||||
overrides?: Partial<DataSourceSettings<T>> |
||||
): DataSourceSettings<T> => |
||||
merge( |
||||
{ |
||||
access: '', |
||||
basicAuth: false, |
||||
basicAuthUser: '', |
||||
withCredentials: false, |
||||
database: '', |
||||
id: 13, |
||||
uid: 'x', |
||||
isDefault: false, |
||||
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' }, |
||||
name: 'gdev-prometheus', |
||||
typeName: 'Prometheus', |
||||
orgId: 1, |
||||
readOnly: false, |
||||
type: 'prometheus', |
||||
typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg', |
||||
url: '', |
||||
user: '', |
||||
secureJsonFields: {}, |
||||
}, |
||||
overrides |
||||
); |
@ -1,94 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/live/data/amendTimeSeries.ts
|
||||
import { closestIdx } from '@grafana/data'; |
||||
|
||||
export type Table = [times: number[], ...values: any[][]]; |
||||
|
||||
// prevTable and nextTable are assumed sorted ASC on reference [0] arrays
|
||||
// nextTable is assumed to be contiguous, only edges are checked for overlap
|
||||
// ...so prev: [1,2,5] + next: [3,4,6] -> [1,2,3,4,6]
|
||||
export function amendTable(prevTable: Table, nextTable: Table): Table { |
||||
let [prevTimes] = prevTable; |
||||
let [nextTimes] = nextTable; |
||||
|
||||
let pLen = prevTimes.length; |
||||
let pStart = prevTimes[0]; |
||||
let pEnd = prevTimes[pLen - 1]; |
||||
|
||||
let nLen = nextTimes.length; |
||||
let nStart = nextTimes[0]; |
||||
let nEnd = nextTimes[nLen - 1]; |
||||
|
||||
let outTable: Table; |
||||
|
||||
if (pLen) { |
||||
if (nLen) { |
||||
// append, no overlap
|
||||
if (nStart > pEnd) { |
||||
outTable = prevTable.map((_, i) => prevTable[i].concat(nextTable[i])) as Table; |
||||
} |
||||
// prepend, no overlap
|
||||
else if (nEnd < pStart) { |
||||
outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i])) as Table; |
||||
} |
||||
// full replace
|
||||
else if (nStart <= pStart && nEnd >= pEnd) { |
||||
outTable = nextTable; |
||||
} |
||||
// partial replace
|
||||
else if (nStart > pStart && nEnd < pEnd) { |
||||
} |
||||
// append, with overlap
|
||||
else if (nStart >= pStart) { |
||||
let idx = closestIdx(nStart, prevTimes); |
||||
idx = prevTimes[idx] < nStart ? idx - 1 : idx; |
||||
outTable = prevTable.map((_, i) => prevTable[i].slice(0, idx).concat(nextTable[i])) as Table; |
||||
} |
||||
// prepend, with overlap
|
||||
else if (nEnd >= pStart) { |
||||
let idx = closestIdx(nEnd, prevTimes); |
||||
idx = prevTimes[idx] < nEnd ? idx : idx + 1; |
||||
outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i].slice(idx))) as Table; |
||||
} |
||||
} else { |
||||
outTable = prevTable; |
||||
} |
||||
} else { |
||||
if (nLen) { |
||||
outTable = nextTable; |
||||
} else { |
||||
outTable = [[]]; |
||||
} |
||||
} |
||||
|
||||
return outTable!; |
||||
} |
||||
|
||||
export function trimTable(table: Table, fromTime: number, toTime: number): Table { |
||||
let [times, ...vals] = table; |
||||
let fromIdx: number | undefined; |
||||
let toIdx: number | undefined; |
||||
|
||||
// trim to bounds
|
||||
if (times[0] < fromTime) { |
||||
fromIdx = closestIdx(fromTime, times); |
||||
|
||||
if (times[fromIdx] < fromTime) { |
||||
fromIdx++; |
||||
} |
||||
} |
||||
|
||||
if (times[times.length - 1] > toTime) { |
||||
toIdx = closestIdx(toTime, times); |
||||
|
||||
if (times[toIdx] > toTime) { |
||||
toIdx--; |
||||
} |
||||
} |
||||
|
||||
if (fromIdx != null || toIdx != null) { |
||||
times = times.slice(fromIdx ?? 0, toIdx); |
||||
vals = vals.map(vals2 => vals2.slice(fromIdx ?? 0, toIdx)); |
||||
} |
||||
|
||||
return [times, ...vals]; |
||||
} |
@ -1,11 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/index.ts
|
||||
import { Observable } from 'rxjs'; |
||||
|
||||
import { toEmitValues } from './toEmitValues'; |
||||
import { toEmitValuesWith } from './toEmitValuesWith'; |
||||
import { ObservableMatchers } from './types'; |
||||
|
||||
export const matchers: ObservableMatchers<void, Observable<any>> = { |
||||
toEmitValues, |
||||
toEmitValuesWith, |
||||
}; |
@ -1,141 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.test.ts
|
||||
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<number>; |
||||
|
||||
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<number>; |
||||
|
||||
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<number>; |
||||
|
||||
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<string>; |
||||
|
||||
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<string | number>; |
||||
|
||||
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<string | number>; |
||||
|
||||
await expect(observable).toEmitValues([0, 'an error']); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,91 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.ts
|
||||
import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; |
||||
import { isEqual } from 'lodash'; |
||||
import { Observable, Subscription } from 'rxjs'; |
||||
|
||||
import { expectObservable, forceObservableCompletion } from './utils'; |
||||
|
||||
function passMessage(received: unknown[], expected: unknown[]) { |
||||
return `${matcherHint('.not.toEmitValues')} |
||||
|
||||
Expected observable to emit values: |
||||
${printExpected(expected)} |
||||
Received: |
||||
${printReceived(received)} |
||||
`;
|
||||
} |
||||
|
||||
function failMessage(received: unknown[], expected: unknown[]) { |
||||
return `${matcherHint('.toEmitValues')} |
||||
|
||||
Expected observable to emit values: |
||||
${printExpected(expected)} |
||||
Received: |
||||
${printReceived(received)} |
||||
`;
|
||||
} |
||||
|
||||
function tryExpectations(received: unknown[], expected: unknown[]): 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) { |
||||
const message = err instanceof Error ? err.message : 'An unknown error occurred'; |
||||
return { |
||||
pass: false, |
||||
message: () => message, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
export function toEmitValues(received: Observable<unknown>, expected: unknown[]): Promise<jest.CustomMatcherResult> { |
||||
const failsChecks = expectObservable(received); |
||||
if (failsChecks) { |
||||
return Promise.resolve(failsChecks); |
||||
} |
||||
|
||||
return new Promise((resolve) => { |
||||
const receivedValues: unknown[] = []; |
||||
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); |
||||
}); |
||||
} |
@ -1,154 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.test.ts
|
||||
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<number>; |
||||
|
||||
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<number>; |
||||
|
||||
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<number>; |
||||
|
||||
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<string>; |
||||
|
||||
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']); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,63 +0,0 @@ |
||||
// Core Grafana history
|
||||
import { matcherHint, printReceived } from 'jest-matcher-utils'; |
||||
import { Observable, Subscription } from 'rxjs'; |
||||
|
||||
import { expectObservable, forceObservableCompletion } from './utils'; |
||||
|
||||
function tryExpectations(received: unknown[], expectations: (received: unknown[]) => 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, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Collect all the values emitted by the observables (also errors) and pass them to the expectations functions after |
||||
* the observable ended (or emitted error). If Observable does not complete within OBSERVABLE_TEST_TIMEOUT_IN_MS the |
||||
* test fails. |
||||
*/ |
||||
export function toEmitValuesWith( |
||||
received: Observable<any>, |
||||
expectations: (actual: any[]) => void |
||||
): Promise<jest.CustomMatcherResult> { |
||||
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); |
||||
}); |
||||
} |
@ -1,14 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.ts
|
||||
import { Observable } from 'rxjs'; |
||||
|
||||
export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000; |
||||
|
||||
export type ObservableType<T> = T extends Observable<infer V> ? V : never; |
||||
|
||||
export interface ObservableMatchers<R, T = {}> extends jest.ExpectExtendMap { |
||||
toEmitValues<E = ObservableType<T>>(received: T, expected: E[]): Promise<jest.CustomMatcherResult>; |
||||
toEmitValuesWith<E = ObservableType<T>>( |
||||
received: T, |
||||
expectations: (received: E[]) => void |
||||
): Promise<jest.CustomMatcherResult>; |
||||
} |
@ -1,64 +0,0 @@ |
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/utils.ts
|
||||
import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; |
||||
import { asapScheduler, Subscription, timer, isObservable } from 'rxjs'; |
||||
|
||||
import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; |
||||
|
||||
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: unknown): jest.CustomMatcherResult | null { |
||||
if (received) { |
||||
return null; |
||||
} |
||||
|
||||
return { |
||||
pass: false, |
||||
message: () => `${matcherHint('.toEmitValues')} |
||||
|
||||
Expected ${printReceived(received)} to be ${printExpected('defined')}.`,
|
||||
}; |
||||
} |
||||
|
||||
export function expectObservableToBeObservable(received: unknown): jest.CustomMatcherResult | null { |
||||
if (isObservable(received)) { |
||||
return null; |
||||
} |
||||
|
||||
return { |
||||
pass: false, |
||||
message: () => `${matcherHint('.toEmitValues')} |
||||
|
||||
Expected ${printReceived(received)} to be ${printExpected('an Observable')}.`,
|
||||
}; |
||||
} |
||||
|
||||
export function expectObservable(received: unknown): jest.CustomMatcherResult | null { |
||||
const toBeDefined = expectObservableToBeDefined(received); |
||||
if (toBeDefined) { |
||||
return toBeDefined; |
||||
} |
||||
|
||||
const toBeObservable = expectObservableToBeObservable(received); |
||||
if (toBeObservable) { |
||||
return toBeObservable; |
||||
} |
||||
|
||||
return null; |
||||
} |
@ -1,6 +1,53 @@ |
||||
import { CoreApp, DataQueryRequest, dateTime, rangeUtil, TimeRange } from '@grafana/data'; |
||||
import { merge } from 'lodash'; |
||||
|
||||
import { PromQuery } from '../types'; |
||||
import { |
||||
CoreApp, |
||||
DataQueryRequest, |
||||
DataSourceJsonData, |
||||
DataSourceSettings, |
||||
dateTime, |
||||
rangeUtil, |
||||
TimeRange, |
||||
} from '@grafana/data'; |
||||
|
||||
import { PromOptions, PromQuery } from '../../types'; |
||||
|
||||
export const getMockDataSource = <T extends DataSourceJsonData>( |
||||
overrides?: Partial<DataSourceSettings<T>> |
||||
): DataSourceSettings<T> => |
||||
merge( |
||||
{ |
||||
access: '', |
||||
basicAuth: false, |
||||
basicAuthUser: '', |
||||
withCredentials: false, |
||||
database: '', |
||||
id: 13, |
||||
uid: 'x', |
||||
isDefault: false, |
||||
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' }, |
||||
name: 'gdev-prometheus', |
||||
typeName: 'Prometheus', |
||||
orgId: 1, |
||||
readOnly: false, |
||||
type: 'prometheus', |
||||
typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg', |
||||
url: '', |
||||
user: '', |
||||
secureJsonFields: {}, |
||||
}, |
||||
overrides |
||||
); |
||||
|
||||
export function createDefaultConfigOptions(): DataSourceSettings<PromOptions> { |
||||
return getMockDataSource<PromOptions>({ |
||||
jsonData: { |
||||
timeInterval: '1m', |
||||
queryTimeout: '1m', |
||||
httpMethod: 'GET', |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
export function createDataRequest( |
||||
targets: PromQuery[], |
Loading…
Reference in new issue