diff --git a/.travis.yml b/.travis.yml
index 594a7057..917b8c66 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,3 +5,9 @@ node_js:
- 'lts/*'
- '8'
cache: npm
+script: npm test
+jobs:
+ include:
+ - name: e2e-test
+ script: npm run e2e-test
+ node_js: '8'
diff --git a/package.json b/package.json
index d605150a..d174e730 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
},
"scripts": {
"e2e-test": "env NODE_ENV=dev node test/e2e/index.js | tap-spec",
- "test": "npm run e2e-test",
- "prepublishOnly": "npm test"
+ "test": "env NODE_ENV=dev node test/index.js | tap-spec",
+ "prepublishOnly": "npm test && npm run e2e-test"
}
}
diff --git a/test/e2e/common.js b/test/e2e/common.js
index 8af5243f..ca28371c 100644
--- a/test/e2e/common.js
+++ b/test/e2e/common.js
@@ -4,9 +4,7 @@ const test = require('tape')
const createClient = require('../..')
const vbbProfile = require('../../p/vbb')
-const parseDateTime = require('../../parse/date-time')
-// todo: use a mock profile
const client = createClient(vbbProfile, 'public-transport/hafas-client:test')
test('exposes the profile', (t) => {
@@ -14,37 +12,3 @@ test('exposes the profile', (t) => {
t.equal(client.profile.endpoint, vbbProfile.endpoint)
t.end()
})
-
-test('parseDateTime: works', (t) => {
- const profile = {timezone: 'Europe/Berlin', locale: 'de-DE'}
- const whenStr = '2019-03-18T13:19:10+01:00'
- const when = +new Date(whenStr)
-
- const assert = (args, expected) => {
- const name = args.join(', ')
- const actual = parseDateTime(profile, ...args)
- t.equal(typeof actual, typeof expected, name)
- t.equal(actual, expected, name)
- }
-
- assert(['20190318', '131910', null, false], whenStr)
- assert(['20190318', '131910', null, true], when)
-
- // manual timezone offset
- assert(['20190318', '131910', 60, false], whenStr)
- assert(['20190318', '131910', 60, true], when)
- assert(['20190318', '131910', 120, false], '2019-03-18T13:19:10+02:00')
- assert(['20190318', '131910', 120, true], +new Date('2019-03-18T13:19:10+02:00'))
-
- // day offset
- assert(['20190318', '2131910', null, false], '2019-03-20T13:19:10+01:00')
- assert(['20190318', '2131910', null, true], +new Date('2019-03-20T13:19:10+01:00'))
- assert(['20190318', '02131910', null, false], '2019-03-20T13:19:10+01:00')
- assert(['20190318', '02131910', null, true], +new Date('2019-03-20T13:19:10+01:00'))
-
- // manual timezone offset day offset
- assert(['20190318', '02131910', 150, false], '2019-03-20T13:19:10+02:30')
- assert(['20190318', '02131910', 150, true], +new Date('2019-03-20T13:19:10+02:30'))
-
- t.end()
-})
diff --git a/test/index.js b/test/index.js
index e480eedc..59e53375 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,5 +1,7 @@
'use strict'
+require('./parse')
+
require('./bvg-journey')
require('./vbb-departures')
require('./bvg-radar')
diff --git a/test/parse/date-time.js b/test/parse/date-time.js
new file mode 100644
index 00000000..e5fa181a
--- /dev/null
+++ b/test/parse/date-time.js
@@ -0,0 +1,50 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/date-time')
+
+const profile = {
+ timezone: 'Europe/Berlin',
+ locale: 'de-DE'
+}
+
+test('date & time parsing uses profile.timezone', (t) => {
+ const iso = parse({
+ ...profile, timezone: 'Europe/Moscow'
+ }, '20190819', '203000', undefined, false)
+ t.equal(iso, '2019-08-19T20:30:00+03:00')
+ t.end()
+})
+
+test('date & time parsing returns a timestamp', (t) => {
+ const iso = parse(profile, '20190819', '203000', undefined, false)
+ const ts = parse(profile, '20190819', '203000', undefined, true)
+ t.equal(ts, +new Date(iso))
+ t.equal(ts, 1566239400 * 1000)
+ t.end()
+})
+
+test('date & time parsing uses tzOffset', (t) => {
+ const iso = parse(profile, '20190819', '203000', -120, false)
+ t.equal(iso, '2019-08-19T20:30:00-02:00')
+ t.end()
+})
+
+test('date & time parsing works with day "overflow"', (t) => {
+ const iso = parse(profile, '20190819', '02203000', undefined, false)
+ t.equal(iso, '2019-08-21T20:30:00+02:00')
+ t.end()
+})
+
+// #106
+test('date & time parsing works with day "overflow" & tzOffset', (t) => {
+ const iso = parse(profile, '20190819', '02203000', -120, false)
+ t.equal(iso, '2019-08-21T20:30:00-02:00')
+ t.end()
+})
+
+test('date & time parsing works with summer & winter time', (t) => {
+ const iso = parse(profile, '20190219', '203000', undefined, false)
+ t.equal(iso, '2019-02-19T20:30:00+01:00')
+ t.end()
+})
diff --git a/test/parse/hint.js b/test/parse/hint.js
new file mode 100644
index 00000000..3dff77c8
--- /dev/null
+++ b/test/parse/hint.js
@@ -0,0 +1,56 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/hint')
+
+const profile = {}
+
+test('parses hints correctly', (t) => {
+ const input = {
+ type: 'A',
+ code: 'bf',
+ prio: 123,
+ txtN: 'some text'
+ }
+ const expected = {
+ type: 'hint',
+ code: 'bf',
+ text: 'some text'
+ }
+
+ t.deepEqual(parse(profile, input), expected)
+ t.deepEqual(parse(profile, {
+ ...input, type: 'I'
+ }), expected)
+
+ // alternative trip
+ t.deepEqual(parse(profile, {
+ ...input, type: 'L', jid: 'trip id'
+ }), {
+ ...expected, type: 'status', code: 'alternative-trip', tripId: 'trip id'
+ })
+
+ // type: M
+ t.deepEqual(parse(profile, {
+ ...input, type: 'M', txtS: 'some summary'
+ }), {
+ ...expected, type: 'status', summary: 'some summary'
+ })
+
+ // type: D
+ for (const type of ['D', 'U', 'R', 'N', 'Y']) {
+ t.deepEqual(parse(profile, {...input, type}), {
+ ...expected, type: 'status'
+ })
+ }
+
+ // .code via .icon
+ t.deepEqual(parse(profile, {
+ ...input, code: null, icon: {type: 'cancel'}
+ }), {...expected, code: 'cancelled'})
+
+ // invalid
+ t.equal(parse(profile, {...input, type: 'X'}), null)
+
+ t.end()
+})
diff --git a/test/parse/icon.js b/test/parse/icon.js
new file mode 100644
index 00000000..940b2f97
--- /dev/null
+++ b/test/parse/icon.js
@@ -0,0 +1,66 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/icon')
+
+test('parses icons correctly', (t) => {
+ const profile = {}
+
+ const text = {
+ "res": "BVG",
+ "text": "Berliner Verkehrsbetriebe"
+ }
+ t.deepEqual(parse(profile, text), {
+ type: 'BVG',
+ title: 'Berliner Verkehrsbetriebe'
+ })
+
+ const txtS = {
+ "res": "PROD_BUS",
+ "txtS": "18"
+ }
+ t.deepEqual(parse(profile, txtS), {
+ type: 'PROD_BUS',
+ title: '18'
+ })
+
+ const txt = {
+ "res": "RBB",
+ "txt": "Regionalbus Braunschweig GmbH"
+ }
+ t.deepEqual(parse(profile, txt), {
+ type: 'RBB',
+ title: 'Regionalbus Braunschweig GmbH'
+ })
+
+ const noText = {
+ "res": "attr_bike_r"
+ }
+ t.deepEqual(parse(profile, noText), {
+ type: 'attr_bike_r',
+ title: null
+ })
+
+ const withColor = {
+ "res": "prod_sub_t",
+ "fg": {
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "bg": {
+ "r": 0,
+ "g": 51,
+ "b": 153,
+ "a": 255
+ }
+ }
+ t.deepEqual(parse(profile, withColor), {
+ type: 'prod_sub_t',
+ title: null,
+ fgColor: {r: 255, g: 255, b: 255, a: 255},
+ bgColor: {r: 0, g: 51, b: 153, a: 255}
+ })
+ t.end()
+})
diff --git a/test/parse/index.js b/test/parse/index.js
new file mode 100644
index 00000000..9bbf7e2a
--- /dev/null
+++ b/test/parse/index.js
@@ -0,0 +1,10 @@
+'use strict'
+
+require('./date-time')
+require('./icon')
+require('./operator')
+require('./location')
+require('./when')
+require('./line')
+require('./hint')
+require('./warning')
diff --git a/test/parse/line.js b/test/parse/line.js
new file mode 100644
index 00000000..1dcbe88c
--- /dev/null
+++ b/test/parse/line.js
@@ -0,0 +1,54 @@
+'use strict'
+
+const test = require('tape')
+const parser = require('../../parse/line')
+
+const profile = {
+ products: [
+ {id: 'train', bitmasks: [1]},
+ {id: 'ferry', bitmasks: [2]},
+ {id: 'bus', bitmasks: [4, 8]}
+ ]
+}
+const opt = {}
+const parse = parser(profile, opt, {})
+
+test('parses lines correctly', (t) => {
+ const input = {
+ line: 'foo line',
+ prodCtx: {
+ lineId: 'Foo ',
+ num: 123
+ }
+ }
+ const expected = {
+ type: 'line',
+ id: 'foo',
+ fahrtNr: 123,
+ name: 'foo line',
+ public: true
+ }
+
+ t.deepEqual(parse(input), expected)
+
+ t.deepEqual(parse({
+ ...input, line: null, addName: input.line
+ }), expected)
+ t.deepEqual(parse({
+ ...input, line: null, name: input.line
+ }), expected)
+
+ // no prodCtx.lineId
+ t.deepEqual(parse({
+ ...input, prodCtx: {...input.prodCtx, lineId: null}
+ }), {
+ ...expected, id: 'foo-line'
+ })
+ // no prodCtx
+ t.deepEqual(parse({
+ ...input, prodCtx: undefined
+ }), {
+ ...expected, id: 'foo-line', fahrtNr: null
+ })
+ t.end()
+})
diff --git a/test/parse/location.js b/test/parse/location.js
new file mode 100644
index 00000000..8e9fa0af
--- /dev/null
+++ b/test/parse/location.js
@@ -0,0 +1,101 @@
+'use strict'
+
+const test = require('tape')
+const omit = require('lodash/omit')
+const parse = require('../../parse/location')
+
+const profile = {
+ parseStationName: name => name.toLowerCase(),
+ parseProducts: bitmask => [bitmask]
+}
+const opt = {
+ linesOfStops: false
+}
+
+test('parses an address correctly', (t) => {
+ const input = {
+ type: 'A',
+ name: 'Foo street 3',
+ lid: 'a=b@L=some%20id',
+ crd: {x: 13418027, y: 52515503}
+ }
+
+ const address = parse(profile, opt, null, input)
+ t.deepEqual(address, {
+ type: 'location',
+ id: 'some id',
+ address: 'Foo street 3',
+ latitude: 52.515503,
+ longitude: 13.418027
+ })
+
+ t.end()
+})
+
+test('parses a POI correctly', (t) => {
+ const input = {
+ type: 'P',
+ name: 'some POI',
+ lid: 'a=b@L=some%20id',
+ crd: {x: 13418027, y: 52515503}
+ }
+
+ const poi = parse(profile, opt, null, input)
+ t.deepEqual(poi, {
+ type: 'location',
+ poi: true,
+ id: 'some id',
+ name: 'some POI',
+ latitude: 52.515503,
+ longitude: 13.418027
+ })
+
+ const withExtId = parse(profile, opt, null, {...input, extId: 'some ext id'})
+ t.equal(withExtId.id, 'some ext id')
+
+ const withLeadingZero = parse(profile, opt, null, {...input, extId: '00some ext id'})
+ t.equal(withLeadingZero.id, 'some ext id')
+
+ t.end()
+})
+
+test('parses a stop correctly', (t) => {
+ const input = {
+ type: 'S',
+ name: 'Foo bus stop',
+ lid: 'a=b@L=foo%20stop',
+ crd: {x: 13418027, y: 52515503},
+ pCls: 123
+ }
+
+ const stop = parse(profile, opt, null, input)
+ t.deepEqual(stop, {
+ type: 'stop',
+ id: 'foo stop',
+ name: 'foo bus stop', // lower-cased!
+ location: {
+ type: 'location',
+ id: 'foo stop',
+ latitude: 52.515503,
+ longitude: 13.418027
+ },
+ products: [123]
+ })
+
+ const withoutLoc = parse(profile, opt, null, omit(input, ['crd']))
+ t.equal(withoutLoc.location, null)
+
+ const mainMast = parse(profile, opt, null, {...input, isMainMast: true})
+ t.equal(mainMast.type, 'station')
+
+ const meta = parse(profile, opt, null, {...input, meta: 1})
+ t.equal(meta.isMeta, true)
+
+ const lineA = {id: 'a'}
+ const withLines = parse(profile, {...opt, linesOfStops: true}, null, {
+ ...input, lines: [lineA]
+ })
+ t.deepEqual(withLines.lines, [lineA])
+
+ t.end()
+})
diff --git a/test/parse/operator.js b/test/parse/operator.js
new file mode 100644
index 00000000..6f73f329
--- /dev/null
+++ b/test/parse/operator.js
@@ -0,0 +1,21 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/operator')
+
+test('parses an operator correctly', (t) => {
+ const profile = {}
+
+ const op = {
+ "name": "Berliner Verkehrsbetriebe",
+ "icoX": 1,
+ "id": "Berliner Verkehrsbetriebe"
+ }
+
+ t.deepEqual(parse(profile, op), {
+ type: 'operator',
+ id: 'berliner-verkehrsbetriebe',
+ name: 'Berliner Verkehrsbetriebe'
+ })
+ t.end()
+})
diff --git a/test/parse/warning.js b/test/parse/warning.js
new file mode 100644
index 00000000..4bce1b16
--- /dev/null
+++ b/test/parse/warning.js
@@ -0,0 +1,63 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/warning')
+
+const profile = {
+ parseProducts: bitmask => [bitmask],
+ parseDateTime: (_, date, time) => date + ':' + time
+}
+
+test('parses warnings correctly', (t) => {
+ const input = {
+ hid: 'some warning ID', // todo: null
+ head: 'some
summary', // todo: null
+ text: 'some long
text
body', // todo: null
+ icon: {type: 'HimWarn'}, // todo: null
+ prio: 123,
+ cat: 1
+ }
+ const expected = {
+ id: 'some warning ID',
+ type: 'status',
+ summary: 'some\nsummary',
+ text: 'some long\ntext\n\nbody',
+ icon: {type: 'HimWarn'},
+ priority: 123,
+ category: 1
+ }
+
+ t.deepEqual(parse(profile, input), expected)
+
+ // without basic fields
+ t.deepEqual(parse(profile, {...input, hid: null}), {...expected, id: null})
+ t.deepEqual(parse(profile, {...input, head: null}), {...expected, summary: null})
+ t.deepEqual(parse(profile, {...input, text: null}), {...expected, text: null})
+ t.deepEqual(parse(profile, {...input, cat: null}), {...expected, category: null})
+
+ // without icon
+ t.deepEqual(parse(profile, {...input, icon: null}), {
+ ...expected, type: 'warning', icon: null
+ })
+
+ // with products
+ t.deepEqual(parse(profile, {...input, prod: 123}), {
+ ...expected, products: [123]
+ })
+
+ // validFrom, validUntil, modified
+ t.deepEqual(parse(profile, {
+ ...input,
+ sDate: '20190101', sTime: '094020',
+ eDate: '20190101', eTime: '114020',
+ lModDate: '20190101', lModTime: '084020',
+ }), {
+ ...expected,
+ validFrom: '20190101:094020',
+ validUntil: '20190101:114020',
+ modified: '20190101:084020'
+ })
+
+ // todo: .edges, .events
+ t.end()
+})
diff --git a/test/parse/when.js b/test/parse/when.js
new file mode 100644
index 00000000..c5160d9a
--- /dev/null
+++ b/test/parse/when.js
@@ -0,0 +1,38 @@
+'use strict'
+
+const test = require('tape')
+const parse = require('../../parse/when')
+
+const profile = {
+ parseDateTime: (profile, date, time, tzOffset, timestamp = false) => {
+ if (timestamp) return ((date + '' + time) - tzOffset * 60) * 1000
+ return date + ':' + time
+ }
+}
+
+test('parseWhen works correctly', (t) => {
+ const date = '20190606'
+ const timeS = '163000'
+ const timeR = '163130'
+ const tzOffset = 120
+ const expected = {
+ when: '20190606:163130',
+ plannedWhen: '20190606:163000',
+ delay: 130 // seconds
+ }
+
+ t.deepEqual(parse(profile, date, timeS, timeR, tzOffset), expected)
+
+ // no realtime data
+ t.deepEqual(parse(profile, date, timeS, null, tzOffset), {
+ ...expected, when: expected.plannedWhen, delay: null
+ })
+
+ // cancelled
+ t.deepEqual(parse(profile, date, timeS, timeR, tzOffset, true), {
+ ...expected,
+ when: null,
+ prognosedWhen: expected.when
+ })
+ t.end()
+})