Skip to content

Commit 1d1846d

Browse files
committed
- Migrate to AVA
- ZWJ support
1 parent 9a6192b commit 1d1846d

File tree

6 files changed

+146
-140
lines changed

6 files changed

+146
-140
lines changed

.eslintrc

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
22
"extends": ["standard"],
3-
"plugins": ["mocha"],
4-
"env": {
5-
"mocha": true
6-
}
3+
"plugins": ["ava"]
74
}

index.js

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
'use strict'
22

3-
const HIGH_SURROGATE_START = 0xD800
4-
const HIGH_SURROGATE_END = 0xDBFF
3+
const HIGH_SURROGATE_START = 0xd800
4+
const HIGH_SURROGATE_END = 0xdbff
55

6-
const LOW_SURROGATE_START = 0xDC00
6+
const LOW_SURROGATE_START = 0xdc00
77

8-
const REGIONAL_INDICATOR_START = 0x1F1E6
9-
const REGIONAL_INDICATOR_END = 0x1F1FF
8+
const REGIONAL_INDICATOR_START = 0x1f1e6
9+
const REGIONAL_INDICATOR_END = 0x1f1ff
1010

1111
const FITZPATRICK_MODIFIER_START = 0x1f3fb
1212
const FITZPATRICK_MODIFIER_END = 0x1f3ff
1313

14-
const VARIATION_MODIFIER_START = 0xFE00
15-
const VARIATION_MODIFIER_END = 0xFE0F
14+
const VARIATION_MODIFIER_START = 0xfe00
15+
const VARIATION_MODIFIER_END = 0xfe0f
16+
17+
const ZWJ = 0x200d
1618

1719
function runes (string) {
1820
if (typeof string !== 'string') {
1921
throw new Error('string cannot be undefined or null')
2022
}
2123
const result = []
2224
let i = 0
23-
let increment
25+
let increment = 0
2426
while (i < string.length) {
25-
increment = nextUnits(i, string)
27+
increment += nextUnits(i + increment, string)
28+
if (isZeroWidthJoiner(string[i + increment])) {
29+
increment++
30+
continue
31+
}
2632
result.push(string.substring(i, i + increment))
2733
i += increment
34+
increment = 0
2835
}
2936
return result
3037
}
@@ -37,7 +44,6 @@ function runes (string) {
3744
// Variations: 2 code units
3845
function nextUnits (i, string) {
3946
const current = string[i]
40-
4147
// If we have variation selector at next position, we can handle it as pair
4248
if (isVariationSelector(string[i + 1])) {
4349
return 2
@@ -90,6 +96,10 @@ function isVariationSelector (string) {
9096
return typeof string === 'string' && betweenInclusive(string.charCodeAt(0), VARIATION_MODIFIER_START, VARIATION_MODIFIER_END)
9197
}
9298

99+
function isZeroWidthJoiner (string) {
100+
return typeof string === 'string' && string.charCodeAt(0) === ZWJ
101+
}
102+
93103
function codePointFromSurrogatePair (pair) {
94104
const highOffset = pair.charCodeAt(0) - HIGH_SURROGATE_START
95105
const lowOffset = pair.charCodeAt(1) - LOW_SURROGATE_START

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
{
22
"name": "runes",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Unicode-aware JS string splitting",
55
"main": "index.js",
66
"scripts": {
7-
"test": "eslint . && mocha test"
7+
"test": "eslint . && ava test"
88
},
99
"repository": {
1010
"type": "git",
1111
"url": "git+ssh://[email protected]/dotcypress/runes.git"
1212
},
1313
"keywords": [
1414
"unicode",
15+
"emoji",
1516
"runes",
1617
"split",
1718
"split string"
@@ -29,12 +30,12 @@
2930
"index.js"
3031
],
3132
"devDependencies": {
33+
"ava": "^0.16.0",
3234
"eslint": "^3.3.1",
33-
"eslint-config-standard": "^5.3.1",
34-
"eslint-plugin-mocha": "^4.3.0",
35+
"eslint-config-standard": "^6.0.0",
36+
"eslint-plugin-ava": "^3.0.0",
3537
"eslint-plugin-promise": "^2.0.1",
3638
"eslint-plugin-standard": "^2.0.0",
37-
"mocha": "^3.0.2",
3839
"should": "^11.1.0"
3940
}
4041
}

readme.md

+14-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[![Build Status](https://img.shields.io/travis/dotcypress/runes.svg?branch=master&style=flat-square)](https://travis-ci.org/dotcypress/runes)
33
[![NPM Version](https://img.shields.io/npm/v/runes.svg?style=flat-square)](https://www.npmjs.com/package/runes)
44

5-
Unicode-aware JS string splitting
5+
Unicode-aware JS string splitting with full Emoji support.
66

77
Split a string into its constituent characters, without munging emoji and other non-BMP code points.
88

@@ -19,13 +19,22 @@ $ npm install runes
1919
## Example
2020

2121
```js
22-
2322
const runes = require('runes')
2423

25-
const example = 'Emoji 🤖'
24+
// Standard String.split
25+
'♥️'.split('') => ['', '']
26+
'Emoji 🤖'.split('') => ['E', 'm', 'o', 'j', 'i', ' ', '', '']
27+
'👩‍👩‍👧‍👦'.split('') => ['', '', '', '', '', '', '', '', '', '', '']
28+
29+
// ES6 string iterator
30+
[...'♥️'] => [ '', '' ]
31+
[...'Emoji 🤖'] => [ 'E', 'm', 'o', 'j', 'i', ' ', '🤖' ]
32+
[...'👩‍👩‍👧‍👦'] => [ '👩', '', '👩', '', '👧', '', '👦' ]
2633

27-
example.split('') // ["E", "m", "o", "j", "i", " ", "�", "�"]
28-
runes(example) // ["E", "m", "o", "j", "i", " ", "🤖"]
34+
// Runes
35+
runes('♥️') => ['♥️']
36+
runes('Emoji 🤖') => ['E', 'm', 'o', 'j', 'i', ' ', '🤖']
37+
runes('👩‍👩‍👧‍👦') => ['👩‍👩‍👧‍👦']
2938

3039
```
3140

test/index.js

-116
This file was deleted.

test/runes.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use strict'
2+
3+
require('should')
4+
const test = require('ava')
5+
6+
const runes = require('../')
7+
8+
test('✂️ Runes should handle emoji in middle', (t) => {
9+
runes('abc😤def').should.be.deepEqual(['a', 'b', 'c', '😤', 'd', 'e', 'f'])
10+
})
11+
12+
test('✂️ Runes should handle leading emoji', (t) => {
13+
runes('🍕abd').should.be.deepEqual(['🍕', 'a', 'b', 'd'])
14+
})
15+
16+
test('✂️ Runes should handle emoji on end', (t) => {
17+
runes('123🍥').should.be.deepEqual(['1', '2', '3', '🍥'])
18+
})
19+
20+
test('✂️ Runes should handle emoji', (t) => {
21+
runes('🍕⚽⛵✨⏳☕⏰🇯🇲😍👍💅😋👭👯✊👸🏽❤️').should.be.deepEqual([
22+
'🍕', '⚽', '⛵', '✨', '⏳', '☕', '⏰', '🇯🇲',
23+
'😍', '👍', '💅', '😋', '👭', '👯', '✊', '👸🏽', '❤️'
24+
])
25+
})
26+
27+
test('✂️ Runes should handle checkmark', (t) => {
28+
runes('123🍕✓').should.be.deepEqual(['1', '2', '3', '🍕', '✓'])
29+
})
30+
31+
test('✂️ Runes should handle ZERO WIDTH JOINER', (t) => {
32+
runes('👨‍👩‍👧').should.be.deepEqual(['👨‍👩‍👧'])
33+
})
34+
35+
test('✂️ Runes should handle ZERO WIDTH JOINER', (t) => {
36+
runes('👨‍👨‍👧‍👧').should.be.deepEqual(['👨‍👨‍👧‍👧'])
37+
})
38+
39+
test('✂️ Runes should reverse', (t) => {
40+
const reversed = runes('123🍕✓').reverse().join('')
41+
const contReversed = runes(reversed).reverse().join('')
42+
reversed.should.equal('✓🍕321')
43+
contReversed.should.equal('123🍕✓')
44+
})
45+
46+
test('✂️ Runes should handle single char', (t) => {
47+
runes('a').should.be.deepEqual(['a'])
48+
})
49+
50+
test('✂️ Runes should handle regular string', (t) => {
51+
runes('Hello').should.be.deepEqual(['H', 'e', 'l', 'l', 'o'])
52+
})
53+
54+
test('✂️ Runes should handle chinese', (t) => {
55+
const string = '𨭎", "𠬠", and "𩷶"'
56+
const result = runes(string)
57+
result.length.should.equal(16)
58+
result[0].should.equal('𨭎')
59+
result[1].should.equal('"')
60+
result[5].should.equal('𠬠')
61+
result[6].should.equal('"')
62+
result[14].should.equal('𩷶')
63+
result[15].should.equal('"')
64+
})
65+
66+
test('✂️ Runes should handle math script', (t) => {
67+
runes('𝒞𝒯𝒮𝒟').should.be.deepEqual(['𝒞', '𝒯', '𝒮', '𝒟'])
68+
})
69+
70+
test('✂️ Runes should handle fraktur', (t) => {
71+
runes('𝔅𝔎').should.be.deepEqual(['𝔅', '𝔎'])
72+
})
73+
74+
test('✂️ Runes should handle acrophonic', (t) => {
75+
const string = '𐅧, 𐅨, and 𐅩'
76+
const result = runes(string)
77+
result.length.should.equal(11)
78+
result[0].should.equal('𐅧')
79+
result[1].should.equal(',')
80+
result[3].should.equal('𐅨')
81+
result[4].should.equal(',')
82+
result[10].should.equal('𐅩')
83+
})
84+
85+
test('✂️ Runes should handle arabic', (t) => {
86+
runes('ځڂڃڄڅچڇڈ').should.be.deepEqual(['ځ', 'ڂ', 'ڃ', 'ڄ', 'څ', 'چ', 'ڇ', 'ڈ'])
87+
})
88+
89+
test('✂️ Runes should handle skin tone indicators', (t) => {
90+
runes('🎅🏻🎅🏼🎅🏽🎅🏾🎅🏿').should.be.deepEqual(['🎅🏻', '🎅🏼', '🎅🏽', '🎅🏾', '🎅🏿'])
91+
})
92+
93+
test('✂️ Runes should handle country flags/regional indicator characters', (t) => {
94+
runes('🇦🇸').should.be.deepEqual(['🇦🇸'])
95+
})
96+
97+
test('✂️ Runes should handle empty string', (t) => {
98+
runes('').should.be.deepEqual([])
99+
})
100+
101+
test('✂️ Runes should throw for null and undefined', (t) => {
102+
(function () {
103+
runes()
104+
}).should.throw()
105+
})

0 commit comments

Comments
 (0)