fable 3.1.62 → 3.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1384,5 +1384,774 @@ suite
1384
1384
  );
1385
1385
  }
1386
1386
  );
1387
+ suite
1388
+ (
1389
+ 'Multi-Row Map Expressions',
1390
+ function ()
1391
+ {
1392
+ test
1393
+ (
1394
+ 'Test basic MULTIROWMAP with current row',
1395
+ (fDone) =>
1396
+ {
1397
+ let testFable = new libFable();
1398
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1399
+ let tmpManifest = testFable.newManyfest();
1400
+
1401
+ let tmpDataSourceObject = {
1402
+ Rows: [
1403
+ { Width: 10, Height: 20 },
1404
+ { Width: 30, Height: 40 },
1405
+ { Width: 50, Height: 60 },
1406
+ ]
1407
+ };
1408
+ let tmpDataDestinationObject = {};
1409
+
1410
+ // Simple: reference current row values (OFFSET 0 is default)
1411
+ let tmpResult = _Parser.solve(
1412
+ 'Areas = MULTIROWMAP ROWS FROM Rows VAR w FROM Width VAR h FROM Height : w * h',
1413
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1414
+
1415
+ Expect(tmpResult).to.be.an('array');
1416
+ Expect(tmpResult.length).to.equal(3);
1417
+ Expect(tmpResult[0]).to.equal('200');
1418
+ Expect(tmpResult[1]).to.equal('1200');
1419
+ Expect(tmpResult[2]).to.equal('3000');
1420
+
1421
+ return fDone();
1422
+ }
1423
+ );
1424
+ test
1425
+ (
1426
+ 'Test MULTIROWMAP with previous row lookback (OFFSET -1)',
1427
+ (fDone) =>
1428
+ {
1429
+ let testFable = new libFable();
1430
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1431
+ let tmpManifest = testFable.newManyfest();
1432
+
1433
+ let tmpDataSourceObject = {
1434
+ Rows: [
1435
+ { Value: 100 },
1436
+ { Value: 200 },
1437
+ { Value: 300 },
1438
+ { Value: 400 },
1439
+ ]
1440
+ };
1441
+ let tmpDataDestinationObject = {};
1442
+
1443
+ // Reference the current row and the previous row's value
1444
+ let tmpResult = _Parser.solve(
1445
+ 'Deltas = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value OFFSET 0 VAR Previous FROM Value OFFSET -1 DEFAULT 0 : Current - Previous',
1446
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1447
+
1448
+ Expect(tmpResult).to.be.an('array');
1449
+ Expect(tmpResult.length).to.equal(4);
1450
+ // Row 0: 100 - 0 (default) = 100
1451
+ Expect(tmpResult[0]).to.equal('100');
1452
+ // Row 1: 200 - 100 = 100
1453
+ Expect(tmpResult[1]).to.equal('100');
1454
+ // Row 2: 300 - 200 = 100
1455
+ Expect(tmpResult[2]).to.equal('100');
1456
+ // Row 3: 400 - 300 = 100
1457
+ Expect(tmpResult[3]).to.equal('100');
1458
+
1459
+ return fDone();
1460
+ }
1461
+ );
1462
+ test
1463
+ (
1464
+ 'Test MULTIROWMAP with two-row lookback (OFFSET -2)',
1465
+ (fDone) =>
1466
+ {
1467
+ let testFable = new libFable();
1468
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1469
+ let tmpManifest = testFable.newManyfest();
1470
+
1471
+ let tmpDataSourceObject = {
1472
+ Rows: [
1473
+ { Value: 5 },
1474
+ { Value: 10 },
1475
+ { Value: 20 },
1476
+ { Value: 35 },
1477
+ { Value: 55 },
1478
+ ]
1479
+ };
1480
+ let tmpDataDestinationObject = {};
1481
+
1482
+ // Second derivative: current - 2*previous + two-back
1483
+ let tmpResult = _Parser.solve(
1484
+ 'SecondDeriv = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value OFFSET 0 VAR Prev FROM Value OFFSET -1 DEFAULT 0 VAR TwoBack FROM Value OFFSET -2 DEFAULT 0 : Current - 2 * Prev + TwoBack',
1485
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1486
+
1487
+ Expect(tmpResult).to.be.an('array');
1488
+ Expect(tmpResult.length).to.equal(5);
1489
+ // Row 0: 5 - 2*0 + 0 = 5
1490
+ Expect(tmpResult[0]).to.equal('5');
1491
+ // Row 1: 10 - 2*5 + 0 = 0
1492
+ Expect(tmpResult[1]).to.equal('0');
1493
+ // Row 2: 20 - 2*10 + 5 = 5
1494
+ Expect(tmpResult[2]).to.equal('5');
1495
+ // Row 3: 35 - 2*20 + 10 = 5
1496
+ Expect(tmpResult[3]).to.equal('5');
1497
+ // Row 4: 55 - 2*35 + 20 = 5
1498
+ Expect(tmpResult[4]).to.equal('5');
1499
+
1500
+ return fDone();
1501
+ }
1502
+ );
1503
+ test
1504
+ (
1505
+ 'Test MULTIROWMAP with three-row lookback (OFFSET -3)',
1506
+ (fDone) =>
1507
+ {
1508
+ let testFable = new libFable();
1509
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1510
+ let tmpManifest = testFable.newManyfest();
1511
+
1512
+ let tmpDataSourceObject = {
1513
+ Rows: [
1514
+ { Value: 1 },
1515
+ { Value: 2 },
1516
+ { Value: 4 },
1517
+ { Value: 8 },
1518
+ { Value: 16 },
1519
+ ]
1520
+ };
1521
+ let tmpDataDestinationObject = {};
1522
+
1523
+ // Sum of current + three rows back
1524
+ let tmpResult = _Parser.solve(
1525
+ 'ThreeBackSum = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR ThreeBack FROM Value OFFSET -3 DEFAULT 0 : Current + ThreeBack',
1526
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1527
+
1528
+ Expect(tmpResult).to.be.an('array');
1529
+ Expect(tmpResult.length).to.equal(5);
1530
+ // Row 0: 1 + 0 (default, no row at -3) = 1
1531
+ Expect(tmpResult[0]).to.equal('1');
1532
+ // Row 1: 2 + 0 (default, no row at -2) = 2
1533
+ Expect(tmpResult[1]).to.equal('2');
1534
+ // Row 2: 4 + 0 (default, no row at -1) = 4
1535
+ Expect(tmpResult[2]).to.equal('4');
1536
+ // Row 3: 8 + 1 (row 0) = 9
1537
+ Expect(tmpResult[3]).to.equal('9');
1538
+ // Row 4: 16 + 2 (row 1) = 18
1539
+ Expect(tmpResult[4]).to.equal('18');
1540
+
1541
+ return fDone();
1542
+ }
1543
+ );
1544
+ test
1545
+ (
1546
+ 'Test MULTIROWMAP Fibonacci sequence',
1547
+ (fDone) =>
1548
+ {
1549
+ let testFable = new libFable();
1550
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1551
+ let tmpManifest = testFable.newManyfest();
1552
+
1553
+ // Fibonacci: each row = sum of two previous rows
1554
+ // Seed: first two rows have the starting values
1555
+ let tmpDataSourceObject = {
1556
+ FibRows: [
1557
+ { Fib: 0 },
1558
+ { Fib: 1 },
1559
+ { Fib: 1 },
1560
+ { Fib: 2 },
1561
+ { Fib: 3 },
1562
+ { Fib: 5 },
1563
+ { Fib: 8 },
1564
+ { Fib: 13 },
1565
+ ]
1566
+ };
1567
+ let tmpDataDestinationObject = {};
1568
+
1569
+ // Verify: for each row, check that Current == Prev1 + Prev2 (except seed rows)
1570
+ let tmpResult = _Parser.solve(
1571
+ 'FibCheck = MULTIROWMAP ROWS FROM FibRows VAR Current FROM Fib VAR Prev1 FROM Fib OFFSET -1 DEFAULT 0 VAR Prev2 FROM Fib OFFSET -2 DEFAULT 0 : Current - (Prev1 + Prev2)',
1572
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1573
+
1574
+ Expect(tmpResult).to.be.an('array');
1575
+ Expect(tmpResult.length).to.equal(8);
1576
+ // Row 0: 0 - (0 + 0) = 0 (seed)
1577
+ Expect(tmpResult[0]).to.equal('0');
1578
+ // Row 1: 1 - (0 + 0) = 1 (seed)
1579
+ Expect(tmpResult[1]).to.equal('1');
1580
+ // Row 2 onwards should all be 0 since each is the sum of previous two
1581
+ Expect(tmpResult[2]).to.equal('0');
1582
+ Expect(tmpResult[3]).to.equal('0');
1583
+ Expect(tmpResult[4]).to.equal('0');
1584
+ Expect(tmpResult[5]).to.equal('0');
1585
+ Expect(tmpResult[6]).to.equal('0');
1586
+ Expect(tmpResult[7]).to.equal('0');
1587
+
1588
+ return fDone();
1589
+ }
1590
+ );
1591
+ test
1592
+ (
1593
+ 'Test MULTIROWMAP with custom default values',
1594
+ (fDone) =>
1595
+ {
1596
+ let testFable = new libFable();
1597
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1598
+ let tmpManifest = testFable.newManyfest();
1599
+
1600
+ let tmpDataSourceObject = {
1601
+ Rows: [
1602
+ { Value: 10 },
1603
+ { Value: 20 },
1604
+ { Value: 30 },
1605
+ ]
1606
+ };
1607
+ let tmpDataDestinationObject = {};
1608
+
1609
+ // Use negative default for previous row
1610
+ let tmpResult = _Parser.solve(
1611
+ 'WithDefaults = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Previous FROM Value OFFSET -1 DEFAULT -1 : Current + Previous',
1612
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1613
+
1614
+ Expect(tmpResult).to.be.an('array');
1615
+ Expect(tmpResult.length).to.equal(3);
1616
+ // Row 0: 10 + (-1) = 9
1617
+ Expect(tmpResult[0]).to.equal('9');
1618
+ // Row 1: 20 + 10 = 30
1619
+ Expect(tmpResult[1]).to.equal('30');
1620
+ // Row 2: 30 + 20 = 50
1621
+ Expect(tmpResult[2]).to.equal('50');
1622
+
1623
+ return fDone();
1624
+ }
1625
+ );
1626
+ test
1627
+ (
1628
+ 'Test MULTIROWMAP with multiple properties from same row offset',
1629
+ (fDone) =>
1630
+ {
1631
+ let testFable = new libFable();
1632
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1633
+ let tmpManifest = testFable.newManyfest();
1634
+
1635
+ let tmpDataSourceObject = {
1636
+ Measurements: [
1637
+ { Width: 10, Height: 5, Depth: 2 },
1638
+ { Width: 20, Height: 10, Depth: 4 },
1639
+ { Width: 30, Height: 15, Depth: 6 },
1640
+ { Width: 40, Height: 20, Depth: 8 },
1641
+ ]
1642
+ };
1643
+ let tmpDataDestinationObject = {};
1644
+
1645
+ // Compare volume change: current volume minus previous volume
1646
+ let tmpResult = _Parser.solve(
1647
+ 'VolumeDelta = MULTIROWMAP ROWS FROM Measurements VAR w FROM Width VAR h FROM Height VAR d FROM Depth VAR pw FROM Width OFFSET -1 DEFAULT 0 VAR ph FROM Height OFFSET -1 DEFAULT 0 VAR pd FROM Depth OFFSET -1 DEFAULT 0 : (w * h * d) - (pw * ph * pd)',
1648
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1649
+
1650
+ Expect(tmpResult).to.be.an('array');
1651
+ Expect(tmpResult.length).to.equal(4);
1652
+ // Row 0: (10*5*2) - (0*0*0) = 100 - 0 = 100
1653
+ Expect(tmpResult[0]).to.equal('100');
1654
+ // Row 1: (20*10*4) - (10*5*2) = 800 - 100 = 700
1655
+ Expect(tmpResult[1]).to.equal('700');
1656
+ // Row 2: (30*15*6) - (20*10*4) = 2700 - 800 = 1900
1657
+ Expect(tmpResult[2]).to.equal('1900');
1658
+ // Row 3: (40*20*8) - (30*15*6) = 6400 - 2700 = 3700
1659
+ Expect(tmpResult[3]).to.equal('3700');
1660
+
1661
+ return fDone();
1662
+ }
1663
+ );
1664
+ test
1665
+ (
1666
+ 'Test MULTIROWMAP with stepIndex variable',
1667
+ (fDone) =>
1668
+ {
1669
+ let testFable = new libFable();
1670
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1671
+ let tmpManifest = testFable.newManyfest();
1672
+
1673
+ let tmpDataSourceObject = {
1674
+ Rows: [
1675
+ { Value: 100 },
1676
+ { Value: 200 },
1677
+ { Value: 300 },
1678
+ ]
1679
+ };
1680
+ let tmpDataDestinationObject = {};
1681
+
1682
+ // Use stepIndex (row index) in the expression
1683
+ let tmpResult = _Parser.solve(
1684
+ 'Indexed = MULTIROWMAP ROWS FROM Rows VAR v FROM Value : v + stepIndex * 1000',
1685
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1686
+
1687
+ Expect(tmpResult).to.be.an('array');
1688
+ Expect(tmpResult.length).to.equal(3);
1689
+ Expect(tmpResult[0]).to.equal('100');
1690
+ Expect(tmpResult[1]).to.equal('1200');
1691
+ Expect(tmpResult[2]).to.equal('2300');
1692
+
1693
+ return fDone();
1694
+ }
1695
+ );
1696
+ test
1697
+ (
1698
+ 'Test MULTIROWMAP moving average',
1699
+ (fDone) =>
1700
+ {
1701
+ let testFable = new libFable();
1702
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1703
+ let tmpManifest = testFable.newManyfest();
1704
+
1705
+ let tmpDataSourceObject = {
1706
+ Prices: [
1707
+ { Close: 100 },
1708
+ { Close: 110 },
1709
+ { Close: 105 },
1710
+ { Close: 115 },
1711
+ { Close: 120 },
1712
+ ]
1713
+ };
1714
+ let tmpDataDestinationObject = {};
1715
+
1716
+ // 3-period moving average: (current + prev + two-back) / 3
1717
+ let tmpResult = _Parser.solve(
1718
+ 'MA3 = MULTIROWMAP ROWS FROM Prices VAR c0 FROM Close VAR c1 FROM Close OFFSET -1 DEFAULT 0 VAR c2 FROM Close OFFSET -2 DEFAULT 0 : (c0 + c1 + c2) / 3',
1719
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1720
+
1721
+ Expect(tmpResult).to.be.an('array');
1722
+ Expect(tmpResult.length).to.equal(5);
1723
+ // Row 2 onwards has three valid values for a true 3-period MA
1724
+ // Row 2: (105 + 110 + 100) / 3 = 105
1725
+ Expect(tmpResult[2]).to.equal('105');
1726
+ // Row 3: (115 + 105 + 110) / 3 = 110
1727
+ Expect(tmpResult[3]).to.equal('110');
1728
+ // Row 4: (120 + 115 + 105) / 3 = 113.33...
1729
+ Expect(Number(tmpResult[4])).to.be.closeTo(113.333, 0.01);
1730
+
1731
+ return fDone();
1732
+ }
1733
+ );
1734
+ test
1735
+ (
1736
+ 'Test MULTIROWMAP returns undefined for invalid rows address',
1737
+ (fDone) =>
1738
+ {
1739
+ let testFable = new libFable();
1740
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1741
+ let tmpManifest = testFable.newManyfest();
1742
+
1743
+ let tmpDataSourceObject = {};
1744
+ let tmpDataDestinationObject = {};
1745
+
1746
+ // No valid rows
1747
+ let tmpResult = _Parser.solve(
1748
+ 'Bad = MULTIROWMAP ROWS FROM NonExistent VAR v FROM Value : v + 1',
1749
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1750
+
1751
+ Expect(tmpResult).to.be.undefined;
1752
+
1753
+ return fDone();
1754
+ }
1755
+ );
1756
+ test
1757
+ (
1758
+ 'Test MULTIROWMAP with forward row lookback (positive OFFSET)',
1759
+ (fDone) =>
1760
+ {
1761
+ let testFable = new libFable();
1762
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1763
+ let tmpManifest = testFable.newManyfest();
1764
+
1765
+ let tmpDataSourceObject = {
1766
+ Rows: [
1767
+ { Value: 10 },
1768
+ { Value: 20 },
1769
+ { Value: 30 },
1770
+ { Value: 40 },
1771
+ { Value: 50 },
1772
+ ]
1773
+ };
1774
+ let tmpDataDestinationObject = {};
1775
+
1776
+ // Look ahead: current value minus next row's value
1777
+ let tmpResult = _Parser.solve(
1778
+ 'ForwardDelta = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Next FROM Value OFFSET 1 DEFAULT 0 : Next - Current',
1779
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1780
+
1781
+ Expect(tmpResult).to.be.an('array');
1782
+ Expect(tmpResult.length).to.equal(5);
1783
+ // Row 0: 20 - 10 = 10
1784
+ Expect(tmpResult[0]).to.equal('10');
1785
+ // Row 1: 30 - 20 = 10
1786
+ Expect(tmpResult[1]).to.equal('10');
1787
+ // Row 2: 40 - 30 = 10
1788
+ Expect(tmpResult[2]).to.equal('10');
1789
+ // Row 3: 50 - 40 = 10
1790
+ Expect(tmpResult[3]).to.equal('10');
1791
+ // Row 4: 0 (default) - 50 = -50
1792
+ Expect(tmpResult[4]).to.equal('-50');
1793
+
1794
+ return fDone();
1795
+ }
1796
+ );
1797
+ test
1798
+ (
1799
+ 'Test MULTIROWMAP with forward lookback two rows ahead (OFFSET 2)',
1800
+ (fDone) =>
1801
+ {
1802
+ let testFable = new libFable();
1803
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1804
+ let tmpManifest = testFable.newManyfest();
1805
+
1806
+ let tmpDataSourceObject = {
1807
+ Rows: [
1808
+ { Value: 1 },
1809
+ { Value: 2 },
1810
+ { Value: 3 },
1811
+ { Value: 4 },
1812
+ { Value: 5 },
1813
+ ]
1814
+ };
1815
+ let tmpDataDestinationObject = {};
1816
+
1817
+ // Look two ahead
1818
+ let tmpResult = _Parser.solve(
1819
+ 'TwoAhead = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Future FROM Value OFFSET 2 DEFAULT 99 : Future + Current',
1820
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1821
+
1822
+ Expect(tmpResult).to.be.an('array');
1823
+ Expect(tmpResult.length).to.equal(5);
1824
+ // Row 0: 3 + 1 = 4
1825
+ Expect(tmpResult[0]).to.equal('4');
1826
+ // Row 1: 4 + 2 = 6
1827
+ Expect(tmpResult[1]).to.equal('6');
1828
+ // Row 2: 5 + 3 = 8
1829
+ Expect(tmpResult[2]).to.equal('8');
1830
+ // Row 3: 99 (default) + 4 = 103
1831
+ Expect(tmpResult[3]).to.equal('103');
1832
+ // Row 4: 99 (default) + 5 = 104
1833
+ Expect(tmpResult[4]).to.equal('104');
1834
+
1835
+ return fDone();
1836
+ }
1837
+ );
1838
+ test
1839
+ (
1840
+ 'Test MULTIROWMAP with SERIESSTART to skip initial rows',
1841
+ (fDone) =>
1842
+ {
1843
+ let testFable = new libFable();
1844
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1845
+ let tmpManifest = testFable.newManyfest();
1846
+
1847
+ let tmpDataSourceObject = {
1848
+ Rows: [
1849
+ { Value: 100 },
1850
+ { Value: 200 },
1851
+ { Value: 300 },
1852
+ { Value: 400 },
1853
+ { Value: 500 },
1854
+ ]
1855
+ };
1856
+ let tmpDataDestinationObject = {};
1857
+
1858
+ // Start from row 2
1859
+ let tmpResult = _Parser.solve(
1860
+ 'FromRow2 = MULTIROWMAP ROWS FROM Rows SERIESSTART 2 VAR v FROM Value : v + 0',
1861
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1862
+
1863
+ Expect(tmpResult).to.be.an('array');
1864
+ Expect(tmpResult.length).to.equal(3);
1865
+ Expect(tmpResult[0]).to.equal('300');
1866
+ Expect(tmpResult[1]).to.equal('400');
1867
+ Expect(tmpResult[2]).to.equal('500');
1868
+
1869
+ return fDone();
1870
+ }
1871
+ );
1872
+ test
1873
+ (
1874
+ 'Test MULTIROWMAP with negative SERIESSTART (count from end)',
1875
+ (fDone) =>
1876
+ {
1877
+ let testFable = new libFable();
1878
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1879
+ let tmpManifest = testFable.newManyfest();
1880
+
1881
+ let tmpDataSourceObject = {
1882
+ Rows: [
1883
+ { Value: 10 },
1884
+ { Value: 20 },
1885
+ { Value: 30 },
1886
+ { Value: 40 },
1887
+ { Value: 50 },
1888
+ ]
1889
+ };
1890
+ let tmpDataDestinationObject = {};
1891
+
1892
+ // Start from 2 rows before the end
1893
+ let tmpResult = _Parser.solve(
1894
+ 'LastTwo = MULTIROWMAP ROWS FROM Rows SERIESSTART -2 VAR v FROM Value : v + 0',
1895
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1896
+
1897
+ Expect(tmpResult).to.be.an('array');
1898
+ Expect(tmpResult.length).to.equal(2);
1899
+ Expect(tmpResult[0]).to.equal('40');
1900
+ Expect(tmpResult[1]).to.equal('50');
1901
+
1902
+ return fDone();
1903
+ }
1904
+ );
1905
+ test
1906
+ (
1907
+ 'Test MULTIROWMAP with SERIESSTEP -1 (iterate backwards)',
1908
+ (fDone) =>
1909
+ {
1910
+ let testFable = new libFable();
1911
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1912
+ let tmpManifest = testFable.newManyfest();
1913
+
1914
+ let tmpDataSourceObject = {
1915
+ Rows: [
1916
+ { Value: 10 },
1917
+ { Value: 20 },
1918
+ { Value: 30 },
1919
+ { Value: 40 },
1920
+ { Value: 50 },
1921
+ ]
1922
+ };
1923
+ let tmpDataDestinationObject = {};
1924
+
1925
+ // Iterate backwards from the last row
1926
+ let tmpResult = _Parser.solve(
1927
+ 'Reversed = MULTIROWMAP ROWS FROM Rows SERIESSTART -1 SERIESSTEP -1 VAR v FROM Value : v + 0',
1928
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1929
+
1930
+ Expect(tmpResult).to.be.an('array');
1931
+ Expect(tmpResult.length).to.equal(5);
1932
+ Expect(tmpResult[0]).to.equal('50');
1933
+ Expect(tmpResult[1]).to.equal('40');
1934
+ Expect(tmpResult[2]).to.equal('30');
1935
+ Expect(tmpResult[3]).to.equal('20');
1936
+ Expect(tmpResult[4]).to.equal('10');
1937
+
1938
+ return fDone();
1939
+ }
1940
+ );
1941
+ test
1942
+ (
1943
+ 'Test MULTIROWMAP with SERIESSTEP 2 (skip every other row)',
1944
+ (fDone) =>
1945
+ {
1946
+ let testFable = new libFable();
1947
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1948
+ let tmpManifest = testFable.newManyfest();
1949
+
1950
+ let tmpDataSourceObject = {
1951
+ Rows: [
1952
+ { Value: 10 },
1953
+ { Value: 20 },
1954
+ { Value: 30 },
1955
+ { Value: 40 },
1956
+ { Value: 50 },
1957
+ { Value: 60 },
1958
+ ]
1959
+ };
1960
+ let tmpDataDestinationObject = {};
1961
+
1962
+ // Every other row
1963
+ let tmpResult = _Parser.solve(
1964
+ 'EveryOther = MULTIROWMAP ROWS FROM Rows SERIESSTEP 2 VAR v FROM Value : v + 0',
1965
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
1966
+
1967
+ Expect(tmpResult).to.be.an('array');
1968
+ Expect(tmpResult.length).to.equal(3);
1969
+ Expect(tmpResult[0]).to.equal('10');
1970
+ Expect(tmpResult[1]).to.equal('30');
1971
+ Expect(tmpResult[2]).to.equal('50');
1972
+
1973
+ return fDone();
1974
+ }
1975
+ );
1976
+ test
1977
+ (
1978
+ 'Test MULTIROWMAP backwards with lookback still works',
1979
+ (fDone) =>
1980
+ {
1981
+ let testFable = new libFable();
1982
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
1983
+ let tmpManifest = testFable.newManyfest();
1984
+
1985
+ let tmpDataSourceObject = {
1986
+ Rows: [
1987
+ { Value: 10 },
1988
+ { Value: 20 },
1989
+ { Value: 30 },
1990
+ { Value: 40 },
1991
+ ]
1992
+ };
1993
+ let tmpDataDestinationObject = {};
1994
+
1995
+ // Going backwards, previous row (OFFSET -1) means the row with a LOWER index
1996
+ // which is actually the next row in reverse iteration order
1997
+ let tmpResult = _Parser.solve(
1998
+ 'BackwardsWithLook = MULTIROWMAP ROWS FROM Rows SERIESSTART -1 SERIESSTEP -1 VAR Current FROM Value VAR Prev FROM Value OFFSET -1 DEFAULT 0 : Current - Prev',
1999
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
2000
+
2001
+ Expect(tmpResult).to.be.an('array');
2002
+ Expect(tmpResult.length).to.equal(4);
2003
+ // Row 3 (first in reverse): 40 - 30 = 10
2004
+ Expect(tmpResult[0]).to.equal('10');
2005
+ // Row 2: 30 - 20 = 10
2006
+ Expect(tmpResult[1]).to.equal('10');
2007
+ // Row 1: 20 - 10 = 10
2008
+ Expect(tmpResult[2]).to.equal('10');
2009
+ // Row 0: 10 - 0 (default) = 10
2010
+ Expect(tmpResult[3]).to.equal('10');
2011
+
2012
+ return fDone();
2013
+ }
2014
+ );
2015
+ test
2016
+ (
2017
+ 'Test MULTIROWMAP with SERIESSTART and SERIESSTEP combined',
2018
+ (fDone) =>
2019
+ {
2020
+ let testFable = new libFable();
2021
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
2022
+ let tmpManifest = testFable.newManyfest();
2023
+
2024
+ let tmpDataSourceObject = {
2025
+ Rows: [
2026
+ { Value: 10 },
2027
+ { Value: 20 },
2028
+ { Value: 30 },
2029
+ { Value: 40 },
2030
+ { Value: 50 },
2031
+ { Value: 60 },
2032
+ { Value: 70 },
2033
+ { Value: 80 },
2034
+ ]
2035
+ };
2036
+ let tmpDataDestinationObject = {};
2037
+
2038
+ // Start at row 1, step by 3
2039
+ let tmpResult = _Parser.solve(
2040
+ 'SteppedStart = MULTIROWMAP ROWS FROM Rows SERIESSTART 1 SERIESSTEP 3 VAR v FROM Value : v + 0',
2041
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
2042
+
2043
+ Expect(tmpResult).to.be.an('array');
2044
+ // Rows: 1, 4, 7 => Values: 20, 50, 80
2045
+ Expect(tmpResult.length).to.equal(3);
2046
+ Expect(tmpResult[0]).to.equal('20');
2047
+ Expect(tmpResult[1]).to.equal('50');
2048
+ Expect(tmpResult[2]).to.equal('80');
2049
+
2050
+ return fDone();
2051
+ }
2052
+ );
2053
+ test
2054
+ (
2055
+ 'Test MULTIROWMAP with both forward and backward lookback simultaneously',
2056
+ (fDone) =>
2057
+ {
2058
+ let testFable = new libFable();
2059
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
2060
+ let tmpManifest = testFable.newManyfest();
2061
+
2062
+ let tmpDataSourceObject = {
2063
+ Rows: [
2064
+ { Value: 10 },
2065
+ { Value: 20 },
2066
+ { Value: 30 },
2067
+ { Value: 40 },
2068
+ { Value: 50 },
2069
+ ]
2070
+ };
2071
+ let tmpDataDestinationObject = {};
2072
+
2073
+ // Central difference: (next - previous) / 2
2074
+ let tmpResult = _Parser.solve(
2075
+ 'CentralDiff = MULTIROWMAP ROWS FROM Rows VAR Prev FROM Value OFFSET -1 DEFAULT 0 VAR Next FROM Value OFFSET 1 DEFAULT 0 : (Next - Prev) / 2',
2076
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
2077
+
2078
+ Expect(tmpResult).to.be.an('array');
2079
+ Expect(tmpResult.length).to.equal(5);
2080
+ // Row 0: (20 - 0) / 2 = 10
2081
+ Expect(tmpResult[0]).to.equal('10');
2082
+ // Row 1: (30 - 10) / 2 = 10
2083
+ Expect(tmpResult[1]).to.equal('10');
2084
+ // Row 2: (40 - 20) / 2 = 10
2085
+ Expect(tmpResult[2]).to.equal('10');
2086
+ // Row 3: (50 - 30) / 2 = 10
2087
+ Expect(tmpResult[3]).to.equal('10');
2088
+ // Row 4: (0 - 40) / 2 = -20
2089
+ Expect(tmpResult[4]).to.equal('-20');
2090
+
2091
+ return fDone();
2092
+ }
2093
+ );
2094
+ test
2095
+ (
2096
+ 'Test MULTIROWMAP with rowIndex and stepIndex variables',
2097
+ (fDone) =>
2098
+ {
2099
+ let testFable = new libFable();
2100
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
2101
+ let tmpManifest = testFable.newManyfest();
2102
+
2103
+ let tmpDataSourceObject = {
2104
+ Rows: [
2105
+ { Value: 100 },
2106
+ { Value: 200 },
2107
+ { Value: 300 },
2108
+ { Value: 400 },
2109
+ { Value: 500 },
2110
+ ]
2111
+ };
2112
+ let tmpDataDestinationObject = {};
2113
+
2114
+ // With SERIESSTART, rowIndex is the actual array index, stepIndex is the iteration count
2115
+ let tmpResult = _Parser.solve(
2116
+ 'IndexTest = MULTIROWMAP ROWS FROM Rows SERIESSTART 2 VAR v FROM Value : v + rowIndex * 1000 + stepIndex',
2117
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
2118
+
2119
+ Expect(tmpResult).to.be.an('array');
2120
+ Expect(tmpResult.length).to.equal(3);
2121
+ // stepIndex=0, rowIndex=2: 300 + 2000 + 0 = 2300
2122
+ Expect(tmpResult[0]).to.equal('2300');
2123
+ // stepIndex=1, rowIndex=3: 400 + 3000 + 1 = 3401
2124
+ Expect(tmpResult[1]).to.equal('3401');
2125
+ // stepIndex=2, rowIndex=4: 500 + 4000 + 2 = 4502
2126
+ Expect(tmpResult[2]).to.equal('4502');
2127
+
2128
+ return fDone();
2129
+ }
2130
+ );
2131
+ test
2132
+ (
2133
+ 'Test MULTIROWMAP SERIESSTEP zero returns undefined',
2134
+ (fDone) =>
2135
+ {
2136
+ let testFable = new libFable();
2137
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
2138
+ let tmpManifest = testFable.newManyfest();
2139
+
2140
+ let tmpDataSourceObject = {
2141
+ Rows: [{ Value: 1 }]
2142
+ };
2143
+ let tmpDataDestinationObject = {};
2144
+
2145
+ let tmpResult = _Parser.solve(
2146
+ 'BadStep = MULTIROWMAP ROWS FROM Rows SERIESSTEP 0 VAR v FROM Value : v + 0',
2147
+ tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
2148
+
2149
+ Expect(tmpResult).to.be.undefined;
2150
+
2151
+ return fDone();
2152
+ }
2153
+ );
2154
+ }
2155
+ );
1387
2156
  }
1388
2157
  );