From e6f5f1cd853c8f7fd615937024b5dfeb3da39cf8 Mon Sep 17 00:00:00 2001 From: harttle Date: Fri, 5 Jul 2019 12:09:00 +0800 Subject: [PATCH] feat: at_least, at_most, sort_naturual for #132 --- src/builtin/filters/math.ts | 25 +++++++++++ test/integration/builtin/filters/math.ts | 56 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/builtin/filters/math.ts b/src/builtin/filters/math.ts index d774acddd3..b7c8811a45 100644 --- a/src/builtin/filters/math.ts +++ b/src/builtin/filters/math.ts @@ -1,5 +1,9 @@ +const toLowerCase = String.prototype.toLowerCase + export default { 'abs': (v: number) => Math.abs(v), + 'at_least': (v: number, n: number) => Math.max(v, n), + 'at_most': (v: number, n: number) => Math.min(v, n), 'ceil': (v: number) => Math.ceil(v), 'divided_by': (v: number, arg: number) => v / arg, 'floor': (v: number) => Math.floor(v), @@ -10,5 +14,26 @@ export default { return Math.round(v * amp) / amp }, 'plus': (v: number, arg: number) => Number(v) + Number(arg), + 'sort_natural': sortNatural, 'times': (v: number, arg: number) => v * arg } + +function caseInsensitiveCmp (a, b) { + if (!b) return -1 + if (!a) return 1 + a = toLowerCase.call(a) + b = toLowerCase.call(b) + if (a < b) return -1 + if (a > b) return 1 + return 0 +} + +function sortNatural (input: any[], property?: string) { + if (!input || !input.sort) return [] + if (property !== undefined) { + return [...input].sort( + (lhs, rhs) => caseInsensitiveCmp(lhs[property], rhs[property]) + ) + } + return [...input].sort(caseInsensitiveCmp) +} diff --git a/test/integration/builtin/filters/math.ts b/test/integration/builtin/filters/math.ts index fb7eae0331..f929101121 100644 --- a/test/integration/builtin/filters/math.ts +++ b/test/integration/builtin/filters/math.ts @@ -1,12 +1,23 @@ import { expect } from 'chai' import { test, liquid } from '../../../stub/render' +import Liquid from '../../../../src/liquid' describe('filters/math', function () { + const l = new Liquid() + describe('abs', function () { it('should return 3 for -3', () => test('{{ -3 | abs }}', '3')) it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2')) it('should return convert string', () => test('{{ "-3" | abs }}', '3')) }) + describe('at_least', function () { + it('{{4 | at_least: 5}} == 5', () => test('{{ 4 | at_least: 5 }}', '5')) + it('{{4 | at_least: 3}} == 4', () => test('{{ 4 | at_least: 3 }}', '4')) + }) + describe('at_most', function () { + it('{{4 | at_most: 5}} == 4', () => test('{{ 4 | at_most: 5 }}', '4')) + it('{{4 | at_most: 3}} == 3', () => test('{{ 4 | at_most: 3 }}', '3')) + }) describe('ceil', function () { it('should return "2" for 1.2', () => test('{{ 1.2 | ceil }}', '2')) it('should return "2" for 2.0', () => test('{{ 2.0 | ceil }}', '2')) @@ -50,6 +61,51 @@ describe('filters/math', function () { it('should convert first arg as number', () => test('{{ "4" | plus: 2 }}', '6')) it('should convert both args as number', () => test('{{ "4" | plus: "2" }}', '6')) }) + + describe('sort_natural', function () { + it('should sort alphabetically', () => { + return test( + '{% assign my_array = "zebra, octopus, giraffe, Sally Snake" | split: ", " %}{{ my_array | sort_natural | join: ", " }}', + 'giraffe, octopus, Sally Snake, zebra' + ) + }) + it('should sort with specified property', async () => { + const src = '{{ students | sort_natural: "name" | map: "name" | join }}' + const students = [{ name: 'bob' }, { name: 'alice' }, { name: 'carol' }] + const html = await l.parseAndRender(src, { students }) + expect(html).to.equal('alice bob carol') + }) + it('should be stable', async () => { + const src = '{{ students | sort_natural: "age" | map: "name" | join }}' + const students = [ + { name: 'bob', age: 1 }, + { name: 'alice', age: 1 }, + { name: 'carol', age: 1 } + ] + const html = await l.parseAndRender(src, { students }) + expect(html).to.equal('bob alice carol') + }) + it('should tolerate undefined props', async () => { + const src = '{{ students | sort_natural: "age" | map: "name" | join }}' + const students = [ + { name: 'bob' }, + { name: 'alice', age: 2 }, + { name: 'carol' } + ] + const html = await l.parseAndRender(src, { students }) + expect(html).to.equal('alice bob carol') + }) + it('should tolerate non array', async () => { + const src = '{{ students | sort_natural: "age" | map: "name" | join }}' + const html = await l.parseAndRender(src, { students: {} }) + expect(html).to.equal('') + }) + it('should tolerate falsy input', async () => { + const src = '{{ students | sort_natural: "age" | map: "name" | join }}' + const html = await l.parseAndRender(src, { students: undefined }) + expect(html).to.equal('') + }) + }) describe('round', function () { it('should return "1" for 1.2', () => test('{{1.2|round}}', '1')) it('should return "3" for 2.7', () => test('{{2.7|round}}', '3'))