Repository: tradingview/lightweight-charts (ts)

Issue:
OHLC charts render incorrect when `value` is provided
**Lightweight Charts Version:** 1.0.2

**Steps/code to reproduce:**
Render a candlestick or bars chart with data containing a `value`, aside correct `open`, `high`, `low`, `close`

<!--
```javascript
var data = [
	{ time: '2018-10-19', open: 180.34, high: 180.99, low: 178.57, close: 179.85, value: 179.85  },
	{ time: '2018-10-22', open: 180.82, high: 181.40, low: 177.56, close: 178.75 },
	{ time: '2018-10-23', open: 175.77, high: 179.49, low: 175.44, close: 178.53 },
	{ time: '2018-10-24', open: 178.58, high: 182.37, low: 176.31, close: 176.97 },
	{ time: '2018-10-25', open: 177.52, high: 180.50, low: 176.83, close: 179.07 },
]

var chart = LightweightCharts.createChart(document.body, {
  width: 600,
  height: 300,	
})

var candleSeries = chart.addCandlestickSeries({
  priceLineVisible: false,
})

candleSeries.setData(data)

chart.timeScale().setVisibleRange({
  from: data[0].time,
  to: data[data.length-1].time,
})

```
-->

**Actual behavior:**

When `value` is provided the candle doesn't render properly, it's only a thin horizontal line at the close price.

**Expected behavior:**

Ignore value or treat it as close, and render candle properly.

**CodeSandbox/JSFiddle/etc link:**
https://jsfiddle.net/9botcdL5/9/

As you can see the first candle is incorrect.


Pull request:
OHLC charts render incorrect when `value` is provided
**Type of PR:** <!-- bugfix, enhancement, fix typo, etc -->

**PR checklist:**

- [x] Addresses an existing issue: fixes #165 
- [x] Includes tests
- [ ] Documentation update

**Overview of change:**

Getting OHLC/single value depending on series type

<!-- optional -->


Patch:
diff --git a/src/api/data-layer.ts b/src/api/data-layer.ts
index 1048237da..0ba4db6da 100644
--- a/src/api/data-layer.ts
+++ b/src/api/data-layer.ts
@@ -8,6 +8,7 @@ import { Palette } from '../model/palette';
 import { PlotRow, PlotValue } from '../model/plot-data';
 import { Series } from '../model/series';
 import { Bar } from '../model/series-data';
+import { SeriesType } from '../model/series-options';
 import { BusinessDay, TimePoint, TimePointIndex, UTCTimestamp } from '../model/time-data';
 
 import {
@@ -16,6 +17,7 @@ import {
 	isBusinessDay,
 	isUTCTimestamp,
 	LineData,
+	SeriesDataItemTypeMap,
 	Time,
 } from './data-consumer';
 
@@ -70,7 +72,8 @@ function timestampConverter(time: Time): TimePoint {
 	};
 }
 
-type TimedData = Pick<LineData | BarData | HistogramData, 'time'>;
+export type DataItemType = SeriesDataItemTypeMap[SeriesType];
+export type TimedData = Pick<DataItemType, 'time'>;
 
 function selectTimeConverter(data: TimedData[]): TimeConverter | null {
 	if (data.length === 0) {
@@ -95,22 +98,41 @@ export function convertTime(time: Time): TimePoint {
 
 }
 
-function getItemValues(item: TimedData, palette: Palette): Bar['value'] {
-	if ('value' in item) {
-		const val = (item as LineData).value;
-		// default value
-		let color: PlotValue = null;
-		if ('color' in item) {
-			const histItem = item as HistogramData;
-			if (histItem.color !== undefined) {
-				color = palette.addColor(histItem.color);
-			}
+function getLineBasedSeriesItemValue(item: LineData | HistogramData, palette: Palette): Bar['value'] {
+	const val = item.value;
+	// default value
+	let color: PlotValue = null;
+	if ('color' in item) {
+		if (item.color !== undefined) {
+			color = palette.addColor(item.color);
 		}
-		return [val, val, val, val, color];
-	} else {
-		const bar = item as BarData;
-		return [bar.open, bar.high, bar.low, bar.close, null];
 	}
+	return [val, val, val, val, color];
+}
+
+function getOHLCBasedSeriesItemValue(bar: BarData, palette: Palette): Bar['value'] {
+	return [bar.open, bar.high, bar.low, bar.close, null];
+}
+
+// we want to have compile-time checks that the type of the functions is correct
+// but due contravariance we cannot easily use type of values of the SeriesItemValueFnMap map itself
+// so let's use TimedSeriesItemValueFn for shut up the compiler in seriesItemValueFn
+// we need to be sure (and we're sure actually) that stored data has correct type for it's according series object
+type SeriesItemValueFnMap = {
+	[T in keyof SeriesDataItemTypeMap]: (item: SeriesDataItemTypeMap[T], palette: Palette) => Bar['value'];
+};
+type TimedSeriesItemValueFn = (item: TimedData, palette: Palette) => Bar['value'];
+
+const seriesItemValueFnMap: SeriesItemValueFnMap = {
+	Candlestick: getOHLCBasedSeriesItemValue,
+	Bar: getOHLCBasedSeriesItemValue,
+	Area: getLineBasedSeriesItemValue,
+	Histogram: getLineBasedSeriesItemValue,
+	Line: getLineBasedSeriesItemValue,
+};
+
+function seriesItemValueFn(seriesType: SeriesType): TimedSeriesItemValueFn {
+	return seriesItemValueFnMap[seriesType] as TimedSeriesItemValueFn;
 }
 
 function hours(count: number): number {
@@ -177,7 +199,8 @@ function spanByTime(time: TimePoint, previousTime: TimePoint | null): number {
 }
 
 interface TimePointData {
-	mapping: Map<Series, TimedData>;
+	// actually the type of the value should be related to the series' type (generic type)
+	mapping: Map<Series, DataItemType>;
 	index: TimePointIndex;
 	timePoint: TimePoint;
 }
@@ -234,17 +257,17 @@ export class DataLayer {
 		this._sortedTimePoints = [];
 	}
 
-	public setSeriesData(series: Series, data: TimedData[]): UpdatePacket {
+	public setSeriesData<TSeriesType extends SeriesType>(series: Series<TSeriesType>, data: SeriesDataItemTypeMap[TSeriesType][]): UpdatePacket {
 		// palette will be filled inside _setNewPoints
 		series.palette().clear();
 		convertStringsToBusinessDays(data);
 		this._pointDataByTimePoint.forEach((value: TimePointData) => value.mapping.delete(series));
 		const timeConverter = selectTimeConverter(data);
 		if (timeConverter !== null) {
-			data.forEach((item: TimedData) => {
+			data.forEach((item: SeriesDataItemTypeMap[TSeriesType]) => {
 				const time = timeConverter(item.time);
 				const timePointData: TimePointData = this._pointDataByTimePoint.get(time.timestamp) ||
-					{ index: 0 as TimePointIndex, mapping: new Map<Series, TimedData>(), timePoint: time };
+					{ index: 0 as TimePointIndex, mapping: new Map<Series, SeriesDataItemTypeMap[TSeriesType]>(), timePoint: time };
 				timePointData.mapping.set(series, item);
 				this._pointDataByTimePoint.set(time.timestamp, timePointData);
 			});
@@ -265,7 +288,7 @@ export class DataLayer {
 		return this.setSeriesData(series, []);
 	}
 
-	public updateSeriesData(series: Series, data: TimedData): UpdatePacket {
+	public updateSeriesData<TSeriesType extends SeriesType>(series: Series<TSeriesType>, data: SeriesDataItemTypeMap[TSeriesType]): UpdatePacket {
 		// check types
 		convertStringToBusinessDay(data);
 		const bars = series.data().bars();
@@ -286,7 +309,7 @@ export class DataLayer {
 		const changedTimePointTime = ensureNotNull(selectTimeConverter([data]))(data.time);
 
 		const pointData: TimePointData = this._pointDataByTimePoint.get(changedTimePointTime.timestamp) ||
-			{ index: 0 as TimePointIndex, mapping: new Map<Series, TimedData>(), timePoint: changedTimePointTime };
+			{ index: 0 as TimePointIndex, mapping: new Map<Series, SeriesDataItemTypeMap[TSeriesType]>(), timePoint: changedTimePointTime };
 		const newPoint = pointData.mapping.size === 0;
 		pointData.mapping.set(series, data);
 		let updateAllSeries = false;
@@ -312,11 +335,13 @@ export class DataLayer {
 		for (let index = pointData.index; index < this._pointDataByTimePoint.size; ++index) {
 			const timePoint = ensureDefined(this._timePointsByIndex.get(index));
 			const currentIndexData = ensureDefined(this._pointDataByTimePoint.get(timePoint.timestamp));
-			currentIndexData.mapping.forEach((currentData: TimedData, currentSeries: Series) => {
+			currentIndexData.mapping.forEach((currentData: DataItemType, currentSeries: Series) => {
 				if (!updateAllSeries && currentSeries !== series) {
 					return;
 				}
 
+				const getItemValues = seriesItemValueFn(currentSeries.seriesType());
+
 				const packet = seriesUpdates.get(currentSeries) || newSeriesUpdatePacket();
 				const seriesUpdate: PlotRow<Bar['time'], Bar['value']> = {
 					index,
@@ -353,8 +378,9 @@ export class DataLayer {
 		this._sortedTimePoints.forEach((time: TimePoint, index: number) => {
 			const pointData = ensureDefined(this._pointDataByTimePoint.get(time.timestamp));
 			pointData.index = index as TimePointIndex;
-			pointData.mapping.forEach((targetData: TimedData, targetSeries: Series) => {
+			pointData.mapping.forEach((targetData: DataItemType, targetSeries: Series) => {
 				// add point to series
+				const getItemValues = seriesItemValueFn(targetSeries.seriesType());
 				const packet = seriesUpdates.get(targetSeries) || newSeriesUpdatePacket();
 				const seriesUpdate: PlotRow<Bar['time'], Bar['value']> = {
 					index: index as TimePointIndex,
diff --git a/src/model/series.ts b/src/model/series.ts
index e76c3678e..c9d9fc67f 100644
--- a/src/model/series.ts
+++ b/src/model/series.ts
@@ -294,7 +294,7 @@ export class Series<T extends SeriesType = SeriesType> extends PriceDataSource i
 		return this._palette;
 	}
 
-	public seriesType(): SeriesType {
+	public seriesType(): T {
 		return this._seriesType;
 	}
 


Test patch:
diff --git a/tests/e2e/graphics/test-cases/initial-options/invalid-bar-spacing.js b/tests/e2e/graphics/test-cases/initial-options/invalid-bar-spacing.js
index 98427a43e..c967529f3 100644
--- a/tests/e2e/graphics/test-cases/initial-options/invalid-bar-spacing.js
+++ b/tests/e2e/graphics/test-cases/initial-options/invalid-bar-spacing.js
@@ -1,13 +1,23 @@
+function generateBar(i, target) {
+	var step = (i % 20) / 1000;
+	var base = i / 5;
+	target.open = base * (1 - step);
+	target.high = base * (1 + 2 * step);
+	target.low = base * (1 - 2 * step);
+	target.close = base * (1 + step);
+}
+
 function generateData() {
 	var res = [];
 	var time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
 	for (var i = 0; i < 500; ++i) {
-		res.push({
+		var item = {
 			time: time.getTime() / 1000,
-			value: i,
-		});
-
+		};
 		time.setUTCDate(time.getUTCDate() + 1);
+
+		generateBar(i, item);
+		res.push(item);
 	}
 	return res;
 }
diff --git a/tests/e2e/graphics/test-cases/initial-options/time-scale.js b/tests/e2e/graphics/test-cases/initial-options/time-scale.js
index 3271abdb3..99b101183 100644
--- a/tests/e2e/graphics/test-cases/initial-options/time-scale.js
+++ b/tests/e2e/graphics/test-cases/initial-options/time-scale.js
@@ -1,13 +1,23 @@
+function generateBar(i, target) {
+	var step = (i % 20) / 1000;
+	var base = i / 5;
+	target.open = base * (1 - step);
+	target.high = base * (1 + 2 * step);
+	target.low = base * (1 - 2 * step);
+	target.close = base * (1 + step);
+}
+
 function generateData() {
 	var res = [];
 	var time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
 	for (var i = 0; i < 500; ++i) {
-		res.push({
+		var item = {
 			time: time.getTime() / 1000,
-			value: i,
-		});
-
+		};
 		time.setUTCDate(time.getUTCDate() + 1);
+
+		generateBar(i, item);
+		res.push(item);
 	}
 	return res;
 }
diff --git a/tests/unittests/data-layer.spec.ts b/tests/unittests/data-layer.spec.ts
index e5eae591a..375c96cba 100644
--- a/tests/unittests/data-layer.spec.ts
+++ b/tests/unittests/data-layer.spec.ts
@@ -1,26 +1,30 @@
 import { expect } from 'chai';
 import { describe, it } from 'mocha';
 
-import { convertTime, DataLayer, SeriesUpdatePacket, stringToBusinessDay } from '../../src/api/data-layer';
+import { BarData, HistogramData, LineData } from '../../src/api/data-consumer';
+import { convertTime, DataLayer, SeriesUpdatePacket, stringToBusinessDay, TimedData } from '../../src/api/data-layer';
 import { ensureDefined } from '../../src/helpers/assertions';
 import { Palette } from '../../src/model/palette';
 import { Series } from '../../src/model/series';
-import { SeriesData } from '../../src/model/series-data';
+import { SeriesData, SeriesPlotIndex } from '../../src/model/series-data';
+import { SeriesType } from '../../src/model/series-options';
 import { BusinessDay, TimePointIndex, UTCTimestamp } from '../../src/model/time-data';
 
 // TODO: add tests for marks spans
 
-function createSeriesMock(): Series {
+function createSeriesMock<T extends SeriesType = 'Line'>(seriesType?: T): Series<T> {
 	const data = new SeriesData();
 	// tslint:disable-next-line:no-object-literal-type-assertion
 	return {
-		data: () => {
-			return data;
-		},
-		palette: () => {
-			return new Palette();
-		},
-	} as Series;
+		data: () => data,
+		palette: () => new Palette(),
+		seriesType: () => seriesType || 'Line',
+	} as Series<T>;
+}
+
+// just for tests
+function dataItemAt(time: TimedData['time']): BarData & LineData & HistogramData {
+	return { time, value: 0, open: 0, high: 0, low: 0, close: 0 };
 }
 
 describe('DataLayer', () => {
@@ -31,7 +35,7 @@ describe('DataLayer', () => {
 		const series1 = createSeriesMock();
 		const series2 = createSeriesMock();
 
-		const updateResult1 = dataLayer.setSeriesData(series1, [{ time: 1000 as UTCTimestamp }, { time: 3000 as UTCTimestamp }]);
+		const updateResult1 = dataLayer.setSeriesData(series1, [dataItemAt(1000 as UTCTimestamp), dataItemAt(3000 as UTCTimestamp)]);
 		expect(updateResult1.timeScaleUpdate.index).to.be.equal(0 as TimePointIndex);
 		expect(updateResult1.timeScaleUpdate.changes).to.have.deep.members([{ timestamp: 1000 }, { timestamp: 3000 }]);
 		expect(updateResult1.timeScaleUpdate.seriesUpdates.size).to.be.equal(1);
@@ -51,7 +55,7 @@ describe('DataLayer', () => {
 		expect(updateResult1.timeScaleUpdate.marks[1].index).to.be.equal(1 as TimePointIndex);
 		expect(updateResult1.timeScaleUpdate.marks[1].time).to.be.deep.equal({ timestamp: 3000 });
 
-		const updateResult2 = dataLayer.setSeriesData(series2, [{ time: 2000 as UTCTimestamp }, { time: 4000 as UTCTimestamp }]);
+		const updateResult2 = dataLayer.setSeriesData(series2, [dataItemAt(2000 as UTCTimestamp), dataItemAt(4000 as UTCTimestamp)]);
 		expect(updateResult2.timeScaleUpdate.index).to.be.equal(0 as TimePointIndex);
 		expect(updateResult2.timeScaleUpdate.changes).to.have.deep.members(
 			[{ timestamp: 1000 }, { timestamp: 2000 }, { timestamp: 3000 }, { timestamp: 4000 }]);
@@ -93,9 +97,9 @@ describe('DataLayer', () => {
 		const series2 = createSeriesMock();
 		const series3 = createSeriesMock();
 
-		dataLayer.setSeriesData(series1, [{ time: 2000 as UTCTimestamp }, { time: 5000 as UTCTimestamp }]);
-		dataLayer.setSeriesData(series2, [{ time: 3000 as UTCTimestamp }, { time: 7000 as UTCTimestamp }]);
-		dataLayer.setSeriesData(series3, [{ time: 4000 as UTCTimestamp }, { time: 6000 as UTCTimestamp }]);
+		dataLayer.setSeriesData(series1, [dataItemAt(2000 as UTCTimestamp), dataItemAt(5000 as UTCTimestamp)]);
+		dataLayer.setSeriesData(series2, [dataItemAt(3000 as UTCTimestamp), dataItemAt(7000 as UTCTimestamp)]);
+		dataLayer.setSeriesData(series3, [dataItemAt(4000 as UTCTimestamp), dataItemAt(6000 as UTCTimestamp)]);
 
 		const updateResult = dataLayer.removeSeries(series3);
 
@@ -140,11 +144,11 @@ describe('DataLayer', () => {
 		const series1 = createSeriesMock();
 		const series2 = createSeriesMock();
 
-		dataLayer.setSeriesData(series1, [{ time: 1000 as UTCTimestamp }, { time: 3000 as UTCTimestamp }]);
-		dataLayer.setSeriesData(series2, [{ time: 2000 as UTCTimestamp }, { time: 4000 as UTCTimestamp }]);
+		dataLayer.setSeriesData(series1, [dataItemAt(1000 as UTCTimestamp), dataItemAt(3000 as UTCTimestamp)]);
+		dataLayer.setSeriesData(series2, [dataItemAt(2000 as UTCTimestamp), dataItemAt(4000 as UTCTimestamp)]);
 
 		// add a new point
-		const updateResult1 = dataLayer.updateSeriesData(series1, { time: 5000 as UTCTimestamp });
+		const updateResult1 = dataLayer.updateSeriesData(series1, dataItemAt(5000 as UTCTimestamp));
 		expect(updateResult1.timeScaleUpdate.index).to.be.equal(4 as TimePointIndex);
 		expect(updateResult1.timeScaleUpdate.changes).to.have.deep.members([{ timestamp: 5000 }]);
 		expect(updateResult1.timeScaleUpdate.seriesUpdates.size).to.be.equal(1);
@@ -159,7 +163,7 @@ describe('DataLayer', () => {
 		expect(updateResult1.timeScaleUpdate.marks[0].time).to.be.deep.equal({ timestamp: 5000 });
 
 		// add one more point
-		const updateResult2 = dataLayer.updateSeriesData(series2, { time: 6000 as UTCTimestamp });
+		const updateResult2 = dataLayer.updateSeriesData(series2, dataItemAt(6000 as UTCTimestamp));
 		expect(updateResult2.timeScaleUpdate.index).to.be.equal(5 as TimePointIndex);
 		expect(updateResult2.timeScaleUpdate.changes).to.have.deep.members([{ timestamp: 6000 }]);
 		expect(updateResult2.timeScaleUpdate.seriesUpdates.size).to.be.equal(1);
@@ -181,11 +185,11 @@ describe('DataLayer', () => {
 		const series1 = createSeriesMock();
 		const series2 = createSeriesMock();
 
-		dataLayer.setSeriesData(series1, [{ time: 1000 as UTCTimestamp }, { time: 4000 as UTCTimestamp }]);
-		dataLayer.setSeriesData(series2, [{ time: 2000 as UTCTimestamp }, { time: 4000 as UTCTimestamp }]);
+		dataLayer.setSeriesData(series1, [dataItemAt(1000 as UTCTimestamp), dataItemAt(4000 as UTCTimestamp)]);
+		dataLayer.setSeriesData(series2, [dataItemAt(2000 as UTCTimestamp), dataItemAt(4000 as UTCTimestamp)]);
 
 		// change the last point of the first series
-		const updateResult1 = dataLayer.updateSeriesData(series1, { time: 4000 as UTCTimestamp });
+		const updateResult1 = dataLayer.updateSeriesData(series1, dataItemAt(4000 as UTCTimestamp));
 		expect(updateResult1.timeScaleUpdate.index).to.be.equal(2 as TimePointIndex);
 		expect(updateResult1.timeScaleUpdate.changes.length).to.be.equal(0);
 		expect(updateResult1.timeScaleUpdate.seriesUpdates.size).to.be.equal(1);
@@ -198,7 +202,7 @@ describe('DataLayer', () => {
 		expect(updateResult1.timeScaleUpdate.marks.length).to.be.equal(0);
 
 		// change the last point of the second series
-		const updateResult2 = dataLayer.updateSeriesData(series2, { time: 4000 as UTCTimestamp });
+		const updateResult2 = dataLayer.updateSeriesData(series2, dataItemAt(4000 as UTCTimestamp));
 		expect(updateResult2.timeScaleUpdate.index).to.be.equal(2 as TimePointIndex);
 		expect(updateResult2.timeScaleUpdate.changes.length).to.be.equal(0);
 		expect(updateResult2.timeScaleUpdate.seriesUpdates.size).to.be.equal(1);
@@ -218,11 +222,11 @@ describe('DataLayer', () => {
 		const series1 = createSeriesMock();
 		const series2 = createSeriesMock();
 
-		dataLayer.setSeriesData(series1, [{ time: 5000 as UTCTimestamp }, { time: 6000 as UTCTimestamp }]);
-		dataLayer.setSeriesData(series2, [{ time: 2000 as UTCTimestamp }, { time: 3000 as UTCTimestamp }]);
+		dataLayer.setSeriesData(series1, [dataItemAt(5000 as UTCTimestamp), dataItemAt(6000 as UTCTimestamp)]);
+		dataLayer.setSeriesData(series2, [dataItemAt(2000 as UTCTimestamp), dataItemAt(3000 as UTCTimestamp)]);
 
 		// add a new point in the end of one series but not in the end of all points
-		const updateResult = dataLayer.updateSeriesData(series2, { time: 4000 as UTCTimestamp });
+		const updateResult = dataLayer.updateSeriesData(series2, dataItemAt(4000 as UTCTimestamp));
 		expect(updateResult.timeScaleUpdate.index).to.be.equal(2 as TimePointIndex);
 		expect(updateResult.timeScaleUpdate.changes).to.have.deep.members(
 			[{ timestamp: 4000 }, { timestamp: 5000 }, { timestamp: 6000 }]);
@@ -249,7 +253,7 @@ describe('DataLayer', () => {
 		expect(updateResult.timeScaleUpdate.marks[2].time).to.be.deep.equal({ timestamp: 6000 });
 
 		// add a new point before all points
-		const updateResult2 = dataLayer.updateSeriesData(series2, { time: 1000 as UTCTimestamp });
+		const updateResult2 = dataLayer.updateSeriesData(series2, dataItemAt(1000 as UTCTimestamp));
 		expect(updateResult2.timeScaleUpdate.index).to.be.equal(0 as TimePointIndex);
 		expect(updateResult2.timeScaleUpdate.changes).to.have.deep.members(
 			[{ timestamp: 1000 }, { timestamp: 2000 }, { timestamp: 3000 }, { timestamp: 4000 }, { timestamp: 5000 }, { timestamp: 6000 }]);
@@ -291,12 +295,13 @@ describe('DataLayer', () => {
 		expect(updateResult2.timeScaleUpdate.marks[4].time).to.be.deep.equal({ timestamp: 5000 });
 		expect(updateResult2.timeScaleUpdate.marks[5].time).to.be.deep.equal({ timestamp: 6000 });
 	});
+
 	it('allow business days', () => {
 		const dataLayer = new DataLayer();
 		const series1 = createSeriesMock();
 		const date1: BusinessDay = { day: 1, month: 10, year: 2019 };
 		const date2: BusinessDay = { day: 2, month: 10, year: 2019 };
-		const updateResult1 = dataLayer.setSeriesData(series1, [{ time: date1 }, { time: date2 }]);
+		const updateResult1 = dataLayer.setSeriesData(series1, [dataItemAt(date1), dataItemAt(date2)]);
 
 		expect(updateResult1.timeScaleUpdate.index).to.be.equal(0 as TimePointIndex);
 
@@ -335,28 +340,63 @@ describe('DataLayer', () => {
 		expect(updateResult1.timeScaleUpdate.marks[1].index).to.be.equal(1 as TimePointIndex);
 		expect(updateResult1.timeScaleUpdate.marks[1].time).to.be.deep.equal(timePoint2);
 	});
+
 	it('all points should have same time type', () => {
 		const dataLayer = new DataLayer();
 		const series = createSeriesMock();
-		expect(() => dataLayer.setSeriesData(series, [{ time: 5000 as UTCTimestamp }, { time: { day: 1, month: 10, year: 2019 } }]))
+		expect(() => dataLayer.setSeriesData(series, [dataItemAt(5000 as UTCTimestamp), dataItemAt({ day: 1, month: 10, year: 2019 })]))
 			.to.throw();
 	});
+
 	it('all points should have same time type on updating', () => {
 		const dataLayer = new DataLayer();
 		const series = createSeriesMock();
-		const packet = dataLayer.setSeriesData(series, [{ time: { day: 1, month: 10, year: 2019 } }]);
+		const packet = dataLayer.setSeriesData(series, [dataItemAt({ day: 1, month: 10, year: 2019 })]);
 		const update = ensureDefined(packet.timeScaleUpdate.seriesUpdates.get(series));
 		series.data().bars().merge(update.update);
-		expect(() => dataLayer.updateSeriesData(series, { time: 5000 as UTCTimestamp }))
+		expect(() => dataLayer.updateSeriesData(series, dataItemAt(5000 as UTCTimestamp)))
 			.to.throw();
 	});
+
 	it('convertTime', () => {
 		expect(convertTime(1554792010 as UTCTimestamp)).to.be.deep.equal({ timestamp: 1554792010 });
 		const bd: BusinessDay = { day: 1, month: 10, year: 2018 };
 		expect(convertTime(bd)).to.be.deep.equal({ timestamp: 1538352000, businessDay: bd });
 	});
+
+	it('stringToBusinessDay', () => {
+		expect(stringToBusinessDay('2019-05-01')).to.be.deep.equal({ day: 1, month: 5, year: 2019 });
+		expect(() => stringToBusinessDay('2019-15-01')).to.throw();
+	});
+
 	it('stringToBusinessDay', () => {
 		expect(stringToBusinessDay('2019-05-01')).to.be.deep.equal({ day: 1, month: 5, year: 2019 });
 		expect(() => stringToBusinessDay('2019-15-01')).to.throw();
 	});
+
+	it('should ignore "value" fields on OHLC-based series update', () => {
+		const ohlcBasedTypes: SeriesType[] = ['Bar', 'Candlestick'];
+
+		for (const seriesType of ohlcBasedTypes) {
+			const dataLayer = new DataLayer();
+			const series = createSeriesMock(seriesType);
+
+			const item: BarData & LineData = {
+				time: '2017-01-01',
+				open: 10,
+				high: 15,
+				low: 5,
+				close: 11,
+				value: 100,
+			};
+
+			const packet = dataLayer.setSeriesData(series, [item]);
+			const update = ensureDefined(packet.timeScaleUpdate.seriesUpdates.get(series));
+
+			expect(update.update[0].value[SeriesPlotIndex.Open]).to.be.equal(item.open);
+			expect(update.update[0].value[SeriesPlotIndex.High]).to.be.equal(item.high);
+			expect(update.update[0].value[SeriesPlotIndex.Low]).to.be.equal(item.low);
+			expect(update.update[0].value[SeriesPlotIndex.Close]).to.be.equal(item.close);
+		}
+	});
 });