-## Showcases
+A productive cli tool to enjoy leetcode!
-`help`/`user`/`list`/`show`/`test`
+Great thanks to leetcode.com, a really awesome website!
+
+⦙ [Releases](https://skygragon.github.io/leetcode-cli/releases) ⦙
+[Install](https://skygragon.github.io/leetcode-cli/install) ⦙
+[Docs](https://skygragon.github.io/leetcode-cli/) ⦙
+[Commands](https://skygragon.github.io/leetcode-cli/commands) ⦙
+[Advanced](https://skygragon.github.io/leetcode-cli/advanced) ⦙
+[Plugins](https://github.com/skygragon/leetcode-cli-plugins) ⦙
-
+* A very [**EFFICIENT**](#quick-start) way to fight questions.
+* [**CACHING**](https://skygragon.github.io/leetcode-cli/advanced#cache) questions to ease offline thinking.
+* [**GENERATING**](https://skygragon.github.io/leetcode-cli/commands#show) source code before coding.
+* Live [**TEST**](https://skygragon.github.io/leetcode-cli/commands#test) and [**SUBMIT**](https://skygragon.github.io/leetcode-cli/commands#submit) with leetcode.com.
+* Download your previous [**SUBMISSION**](https://skygragon.github.io/leetcode-cli/commands#submission).
+* Trace your coding [**STATUS**](https://skygragon.github.io/leetcode-cli/commands#stat).
+* [**AUTO LOGIN**](https://skygragon.github.io/leetcode-cli/advanced#auto-login) among multiple agents with single account.
+* Multiple [**THEMES**](https://skygragon.github.io/leetcode-cli/advanced#color-themes) support.
+* More [**PLUGINS**](https://skygragon.github.io/leetcode-cli/advanced#plugins) to enjoy extra features!
-`test`/`submit`/`stat`/`submission`
+## Screenshot
-
+
## Quick Start
Read help first $ leetcode help
Login with your leetcode account $ leetcode user -l
- Browse all problems $ leetcode list
- Choose one problem $ leetcode show 1 -g -l cpp
+ Browse all questions $ leetcode list
+ Choose one question $ leetcode show 1 -g -l cpp
Coding it!
Run test(s) and pray... $ leetcode test ./two-sum.cpp -t '[3,2,4]\n7'
Submit final solution! $ leetcode submit ./two-sum.cpp
diff --git a/bin/entrypoint b/bin/entrypoint
new file mode 100755
index 00000000..c2ebd337
--- /dev/null
+++ b/bin/entrypoint
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+srcdir=/root/leetcode-cli
+leetcode=$srcdir/bin/leetcode
+
+if [ ! -f "$leetcode" ]; then
+ echo "Unpacking leetcode-cli code ..."
+ mkdir -p $srcdir
+ tar zxf /leetcode-cli.tar.gz -C $srcdir
+fi
+
+export TERM=xterm-256color
+exec $leetcode $@
diff --git a/bin/pkg b/bin/pkg
new file mode 100755
index 00000000..0cc22db1
--- /dev/null
+++ b/bin/pkg
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+
+const arch = require('os').arch();
+var os = process.platform;
+const ver = process.versions.node.split('.')[0];
+
+var bin = './bin/pkg.sh';
+var args = [arch, os, ver];
+
+if (os === 'darwin') {
+ args[1] = 'macos';
+} else if (os === 'win32') {
+ bin = 'cmd.exe';
+ args = ['/c', 'bin\\pkg.bat'].concat(args);
+}
+
+var proc = require('child_process').spawn(bin, args);
+proc.stdout.on('data', x => console.log(x.toString().trimRight('\n')));
+proc.stderr.on('data', x => console.log(x.toString().trimRight('\n')));
+proc.on('close', process.exit);
\ No newline at end of file
diff --git a/bin/pkg.bat b/bin/pkg.bat
new file mode 100644
index 00000000..0294b7f5
--- /dev/null
+++ b/bin/pkg.bat
@@ -0,0 +1,25 @@
+@echo off
+set arch=%1
+set os=%2
+set ver=%3
+
+set dist=dist\
+set file=leetcode-cli.node%ver%.%os%.%arch%.zip
+
+mkdir %dist%
+del /q %dist%\*
+del /q *.zip
+
+for %%x in (company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss) do (
+ echo [%%x]
+ node bin\leetcode ext -i %%x
+ if %ERRORLEVEL% gtr 0 exit /b 1
+)
+
+for /r . %%x in (*.node) do copy %%x %dist%
+call npm run pkg -- node%ver%-%os%-%arch%
+if %ERRORLEVEL% gtr 0 exit /b 1
+
+7z a %file% %dist%
+if %ERRORLEVEL% gtr 0 exit /b 1
+exit 0
\ No newline at end of file
diff --git a/bin/pkg.sh b/bin/pkg.sh
new file mode 100755
index 00000000..abeb778d
--- /dev/null
+++ b/bin/pkg.sh
@@ -0,0 +1,26 @@
+#!/bin/bash -e
+
+arch=$1
+os=$2
+ver=$3
+
+DIST=./dist
+FILE=leetcode-cli.node$ver.$os.$arch.tar.gz
+
+mkdir -p $DIST
+rm -rf $DIST/*
+rm -rf $FILE
+
+plugins="company cookie.chrome cookie.firefox cpp.lint cpp.run github leetcode.cn lintcode solution.discuss"
+
+for plugin in $plugins; do
+ echo "[$plugin]"
+ ./bin/leetcode ext -i $plugin
+done
+
+find node_modules -name "*.node" -exec cp {} $DIST \;
+npm run pkg -- node$ver-$os-$arch
+
+tar zcvf $FILE $DIST
+ls -al $FILE
+exit 0
\ No newline at end of file
diff --git a/colors/blue.json b/colors/blue.json
index 76b7eac0..7577d79d 100644
--- a/colors/blue.json
+++ b/colors/blue.json
@@ -1,6 +1,10 @@
{
- "gray": "#B0C4DE",
- "green": "#66D9EF",
- "red": "#AE81FF",
- "yellow": "#87CEEB"
+ "blue": "#0000ff",
+ "cyan": "#b0c4de",
+ "gray": "#483d8b",
+ "green": "#00bfff",
+ "magenta": "#6a5acd",
+ "red": "#ae81ff",
+ "white": "#f0f8ff",
+ "yellow": "#87cefa"
}
diff --git a/colors/dark.json b/colors/dark.json
index c7b90db3..86e60471 100644
--- a/colors/dark.json
+++ b/colors/dark.json
@@ -1,6 +1,9 @@
{
+ "blue": "#000099",
+ "cyan": "#009999",
"gray": "#455354",
"green": "#009900",
+ "magenta": "#990099",
"red": "#990000",
"yellow": "#999900"
}
diff --git a/colors/molokai.json b/colors/molokai.json
new file mode 100644
index 00000000..1e75d1ea
--- /dev/null
+++ b/colors/molokai.json
@@ -0,0 +1,10 @@
+{
+ "blue": "#66D9EF",
+ "cyan": "#AE81FF",
+ "gray": "#75715E",
+ "green": "#87FF00",
+ "magenta": "#FF46FF",
+ "red": "#D7005F",
+ "white": "#F8F8F2",
+ "yellow": "#FD971F"
+}
diff --git a/colors/orange.json b/colors/orange.json
index ac68279b..2fcc4378 100644
--- a/colors/orange.json
+++ b/colors/orange.json
@@ -1,6 +1,10 @@
{
- "gray": "#C4BE89",
- "green": "#E6DB74",
- "red": "#ef5939",
- "yellow": "#FD971F"
+ "blue": "#808000",
+ "cyan": "#b8860b",
+ "gray": "#deb887",
+ "green": "#ffa500",
+ "magenta": "#d2691e",
+ "red": "#ff4500",
+ "white": "#fdf5eb",
+ "yellow": "#ffd700"
}
diff --git a/colors/pink.json b/colors/pink.json
index 497a5e43..762a8a90 100644
--- a/colors/pink.json
+++ b/colors/pink.json
@@ -1,6 +1,10 @@
{
- "gray": "#BCA3A3",
- "green": "#ff1493",
- "red": "#dc143c",
- "yellow": "#ff4500"
+ "blue": "#8a2be2",
+ "cyan": "#800080",
+ "gray": "#d8bfd8",
+ "green": "#ff00ff",
+ "magenta": "#db7093",
+ "red": "#ff1493",
+ "white": "#fff0f5",
+ "yellow": "#ffc0cb"
}
diff --git a/colors/solarized.json b/colors/solarized.json
new file mode 100644
index 00000000..62069b2b
--- /dev/null
+++ b/colors/solarized.json
@@ -0,0 +1,10 @@
+{
+ "black": "#073642",
+ "blue": "#268bd2",
+ "cyan": "#2aa198",
+ "green": "#859900",
+ "magenta": "#d33682",
+ "red": "#dc322f",
+ "white": "#eee8d5",
+ "yellow": "#b58900"
+}
diff --git a/colors/solarized.light.json b/colors/solarized.light.json
new file mode 100644
index 00000000..e22c61d3
--- /dev/null
+++ b/colors/solarized.light.json
@@ -0,0 +1,10 @@
+{
+ "black": "#262626",
+ "blue": "#0087ff",
+ "cyan": "#00afaf",
+ "green": "#5f8700",
+ "magenta": "#af005f",
+ "red": "#d70000",
+ "white": "#d7d7af",
+ "yellow": "#af8700"
+}
diff --git a/docs/advanced.md b/docs/advanced.md
index 794e9625..42904e91 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -3,21 +3,46 @@ layout: default
title: Advanced Topic
---
+* [Aliases](#aliases)
* [Auto Login](#auto-login)
* [Bash Completion](#bash-completion)
* [Cache](#cache)
* [Configuration](#configuration)
-* [Color Theme](#color-theme)
-* [Log Level](#log-level)
+* [Color Themes](#color-themes)
+* [File Name](#file-name)
+* [Log Levels](#log-levels)
* [Plugins](#plugins)
-# Auto login
+# Aliases
+
+The commands in leetcode-cli usually has builtin aliases as below:
+
+|Command |Aliases |
+|----------|-----------------------|
+|config |conf, cfg, setting |
+|list |ls |
+|plugin |extension, ext |
+|session |branch |
+|show |view, pick |
+|star |like, favorite |
+|stat |stats, progress, report|
+|submission|pull |
+|submit |push, commit |
+|test |run |
+|user |account |
+|version |info, env |
+
+# Auto Login
Leetcode.com is restricting only one session alive in the same time, which means if you login same account otherwhere, the existing login session will be expired immediately. This will greatly harm your experience since you have to re-login again and again among different sessions.
The good news is leetcode-cli will help a lot on this by trying re-login transparently and automatically without interrupting your current work whenever it detects your current session is expired. To enable this feature you could add following in your config then login again:
- "AUTO_LOGIN": true
+ {
+ "autologin": {
+ "enable": true
+ }
+ }
**NOTE: once enabled, your PASSWORD will be persisted locally for further using, so PLEASE be careful to ONLY enable this on your OWN computer for the sake of security!**
@@ -25,12 +50,12 @@ The good news is leetcode-cli will help a lot on this by trying re-login transpa
Copy `.lc-completion.bash` to your home directory, and source it in .bashrc (Linux) or .bash_profile (MacOS).
- $ cp .lc-completion.bash ~
- $ echo "source ~/.lc-completion.bash" >> ~/.bashrc
- $ source ~/.bashrc
+ $ cp .lc-completion.bash ~
+ $ echo "source ~/.lc-completion.bash" >> ~/.bashrc
+ $ source ~/.bashrc
- $ leetcode list --\r\nGiven two strings s and t which consist of only lowercase letters.
\r\n\r\nString t is generated by random shuffling string s and then add one more letter at a random position.
\r\n\r\nFind the letter that was added in t.
\r\n\r\nExample:\r\n
\r\nInput:\r\ns = \"abcd\"\r\nt = \"abcde\"\r\n\r\nOutput:\r\ne\r\n\r\nExplanation:\r\n'e' is the letter that was added.\r\n","stats":"{\"totalAccepted\": \"89.7K\", \"totalSubmission\": \"175.7K\"}","codeDefinition":"[{\"text\": \"C++\", \"value\": \"cpp\", \"defaultCode\": \"class Solution {\\r\\npublic:\\r\\n char findTheDifference(string s, string t) {\\r\\n \\r\\n }\\r\\n};\"}, {\"text\": \"Java\", \"value\": \"java\", \"defaultCode\": \"class Solution {\\r\\n public char findTheDifference(String s, String t) {\\r\\n \\r\\n }\\r\\n}\"}, {\"text\": \"Python\", \"value\": \"python\", \"defaultCode\": \"class Solution(object):\\r\\n def findTheDifference(self, s, t):\\r\\n \\\"\\\"\\\"\\r\\n :type s: str\\r\\n :type t: str\\r\\n :rtype: str\\r\\n \\\"\\\"\\\"\\r\\n \"}, {\"text\": \"Python3\", \"value\": \"python3\", \"defaultCode\": \"class Solution:\\r\\n def findTheDifference(self, s, t):\\r\\n \\\"\\\"\\\"\\r\\n :type s: str\\r\\n :type t: str\\r\\n :rtype: str\\r\\n \\\"\\\"\\\"\\r\\n \"}, {\"text\": \"C\", \"value\": \"c\", \"defaultCode\": \"char findTheDifference(char* s, char* t) {\\r\\n \\r\\n}\"}, {\"text\": \"C#\", \"value\": \"csharp\", \"defaultCode\": \"public class Solution {\\r\\n public char FindTheDifference(string s, string t) {\\r\\n \\r\\n }\\r\\n}\"}, {\"text\": \"JavaScript\", \"value\": \"javascript\", \"defaultCode\": \"/**\\r\\n * @param {string} s\\r\\n * @param {string} t\\r\\n * @return {character}\\r\\n */\\r\\nvar findTheDifference = function(s, t) {\\r\\n \\r\\n};\"}, {\"text\": \"Ruby\", \"value\": \"ruby\", \"defaultCode\": \"# @param {String} s\\r\\n# @param {String} t\\r\\n# @return {Character}\\r\\ndef find_the_difference(s, t)\\r\\n \\r\\nend\"}, {\"text\": \"Swift\", \"value\": \"swift\", \"defaultCode\": \"class Solution {\\r\\n func findTheDifference(_ s: String, _ t: String) -> Character {\\r\\n \\r\\n }\\r\\n}\"}, {\"text\": \"Go\", \"value\": \"golang\", \"defaultCode\": \"func findTheDifference(s string, t string) byte {\\r\\n \\r\\n}\"}, {\"text\": \"Scala\", \"value\": \"scala\", \"defaultCode\": \"object Solution {\\n def findTheDifference(s: String, t: String): Char = {\\n \\n }\\n}\"}, {\"text\": \"Kotlin\", \"value\": \"kotlin\", \"defaultCode\": \"class Solution {\\n fun findTheDifference(s: String, t: String): Char {\\n \\n }\\n}\"}]","sampleTestCase":"\"abcd\"\n\"abcde\"","enableRunCode":true,"metaData":"{\r\n \"name\": \"findTheDifference\",\r\n \"params\": [\r\n {\r\n \"name\": \"s\",\r\n \"type\": \"string\"\r\n },\r\n {\r\n \"name\": \"t\",\r\n \"type\": \"string\"\r\n }\r\n ],\r\n \"return\": {\r\n \"type\": \"character\"\r\n }\r\n}","discussCategoryId":"511"}}} \ No newline at end of file diff --git a/test/plugins/test_cache.js b/test/plugins/test_cache.js index 68f881ba..6b3114ca 100644 --- a/test/plugins/test_cache.js +++ b/test/plugins/test_cache.js @@ -1,48 +1,51 @@ -var execSync = require('child_process').execSync; -var fs = require('fs'); +'use strict'; +const _ = require('underscore'); +const assert = require('chai').assert; +const rewire = require('rewire'); -var _ = require('underscore'); -var assert = require('chai').assert; -var rewire = require('rewire'); - -var log = require('../../lib/log'); -var config = require('../../lib/config'); - -var cache = rewire('../../lib/cache'); -var h = rewire('../../lib/helper'); -var session = rewire('../../lib/session'); -var plugin = rewire('../../lib/plugins/cache'); - -var HOME = './tmp'; +const h = require('../../lib/helper'); +const log = require('../../lib/log'); +const config = require('../../lib/config'); +const th = require('../helper'); describe('plugin:cache', function() { - var PROBLEMS = [ - {id: 0, name: 'name0', slug: 'slug0', starred: false, category: 'algorithms'}, - {id: 1, name: 'name1', slug: 'slug1', starred: true, category: 'algorithms'} + let plugin; + let next; + let cache; + let file; + let session; + + const PROBLEMS = [ + {id: 0, fid: 0, name: 'name0', slug: 'slug0', starred: false, category: 'algorithms'}, + {id: 1, fid: 1, name: 'name1', slug: 'slug1', starred: true, category: 'algorithms'} ]; - var PROBLEM = {id: 0, slug: 'slug0', category: 'algorithms'}; - - var NEXT = {}; + const PROBLEM = {id: 0, fid: 0, slug: 'slug0', category: 'algorithms'}; before(function() { log.init(); config.init(); - plugin.init(); + }); + + beforeEach(function() { + th.clean(); + next = {}; + + file = rewire('../../lib/file'); + file.cacheDir = () => th.DIR; - h.getHomeDir = function() { - return HOME; - }; + cache = rewire('../../lib/cache'); + cache.__set__('file', file); + cache.init(); - cache.__set__('h', h); + session = rewire('../../lib/session'); session.__set__('cache', cache); + + plugin = rewire('../../lib/plugins/cache'); plugin.__set__('cache', cache); plugin.__set__('session', session); - plugin.setNext(NEXT); - }); + plugin.init(); - beforeEach(function() { - execSync('rm -rf ' + HOME); - fs.mkdirSync(HOME); + plugin.setNext(next); }); describe('#getProblems', function() { @@ -58,10 +61,7 @@ describe('plugin:cache', function() { it('should getProblems w/o cache ok', function(done) { cache.del('problems'); - - NEXT.getProblems = function(cb) { - return cb(null, PROBLEMS); - }; + next.getProblems = cb => cb(null, PROBLEMS); plugin.getProblems(function(e, problems) { assert.equal(e, null); @@ -72,10 +72,7 @@ describe('plugin:cache', function() { it('should getProblems w/o cache fail if client error', function(done) { cache.del('problems'); - - NEXT.getProblems = function(cb) { - return cb('client getProblems error'); - }; + next.getProblems = cb => cb('client getProblems error'); plugin.getProblems(function(e, problems) { assert.equal(e, 'client getProblems error'); @@ -99,10 +96,7 @@ describe('plugin:cache', function() { it('should getProblem w/o cache ok', function(done) { cache.set('problems', PROBLEMS); cache.del('0.slug0.algorithms'); - - NEXT.getProblem = function(problem, cb) { - return cb(null, PROBLEMS[0]); - }; + next.getProblem = (problem, cb) => cb(null, PROBLEMS[0]); plugin.getProblem(_.clone(PROBLEM), function(e, problem) { assert.equal(e, null); @@ -114,10 +108,7 @@ describe('plugin:cache', function() { it('should getProblem fail if client error', function(done) { cache.set('problems', PROBLEMS); cache.del('0.slug0.algorithms'); - - NEXT.getProblem = function(problem, cb) { - return cb('client getProblem error'); - }; + next.getProblem = (problem, cb) => cb('client getProblem error'); plugin.getProblem(_.clone(PROBLEM), function(e, problem) { assert.equal(e, 'client getProblem error'); @@ -130,14 +121,14 @@ describe('plugin:cache', function() { it('should ok', function() { cache.del('0.slug0.algorithms'); - var problem = _.clone(PROBLEMS[0]); + const problem = _.clone(PROBLEMS[0]); problem.locked = true; problem.state = 'ac'; - var ret = plugin.saveProblem(problem); + const ret = plugin.saveProblem(problem); assert.equal(ret, true); assert.deepEqual(cache.get('0.slug0.algorithms'), - {id: 0, slug: 'slug0', name: 'name0', category: 'algorithms'}); + {id: 0, fid: 0, slug: 'slug0', name: 'name0', category: 'algorithms'}); }); }); // #saveProblem @@ -145,15 +136,15 @@ describe('plugin:cache', function() { it('should updateProblem ok', function(done) { cache.set('problems', PROBLEMS); - var kv = {value: 'value00'}; - var ret = plugin.updateProblem(PROBLEMS[0], kv); + const kv = {value: 'value00'}; + const ret = plugin.updateProblem(PROBLEMS[0], kv); assert.equal(ret, true); plugin.getProblems(function(e, problems) { assert.equal(e, null); assert.deepEqual(problems, [ - {id: 0, name: 'name0', slug: 'slug0', value: 'value00', starred: false, category: 'algorithms'}, - {id: 1, name: 'name1', slug: 'slug1', starred: true, category: 'algorithms'} + {id: 0, fid: 0, name: 'name0', slug: 'slug0', value: 'value00', starred: false, category: 'algorithms'}, + {id: 1, fid: 1, name: 'name1', slug: 'slug1', starred: true, category: 'algorithms'} ]); done(); }); @@ -161,31 +152,29 @@ describe('plugin:cache', function() { it('should updateProblem fail if no problems found', function() { cache.del('problems'); - var ret = plugin.updateProblem(PROBLEMS[0], {}); + const ret = plugin.updateProblem(PROBLEMS[0], {}); assert.equal(ret, false); }); it('should updateProblem fail if unknown problem', function() { cache.set('problems', [PROBLEMS[1]]); - var ret = plugin.updateProblem(PROBLEMS[0], {}); + const ret = plugin.updateProblem(PROBLEMS[0], {}); assert.equal(ret, false); }); }); // #updateProblem describe('#user', function() { - var USER = {name: 'test-user', pass: 'password'}; - var USER_SAFE = {name: 'test-user'}; + const USER = {name: 'test-user', pass: 'password'}; + const USER_SAFE = {name: 'test-user'}; it('should login ok', function(done) { - config.AUTO_LOGIN = true; + config.autologin.enable = true; // before login - cache.del('.user'); + cache.del(h.KEYS.user); assert.equal(session.getUser(), null); assert.equal(session.isLogin(), false); - NEXT.login = function(user, cb) { - return cb(null, user); - }; + next.login = (user, cb) => cb(null, user); plugin.login(USER, function(e, user) { assert.equal(e, null); @@ -199,12 +188,10 @@ describe('plugin:cache', function() { }); it('should login ok w/ auto login', function(done) { - config.AUTO_LOGIN = false; - cache.del('.user'); + config.autologin.enable = false; + cache.del(h.KEYS.user); - NEXT.login = function(user, cb) { - return cb(null, user); - }; + next.login = (user, cb) => cb(null, user); plugin.login(USER, function(e, user) { assert.equal(e, null); @@ -216,9 +203,7 @@ describe('plugin:cache', function() { }); it('should login fail if client login error', function(done) { - NEXT.login = function(user, cb) { - return cb('client login error'); - }; + next.login = (user, cb) => cb('client login error'); plugin.login(USER, function(e, user) { assert.equal(e, 'client login error'); @@ -228,7 +213,7 @@ describe('plugin:cache', function() { it('should logout ok', function(done) { // before logout - cache.set('.user', USER); + cache.set(h.KEYS.user, USER); assert.deepEqual(session.getUser(), USER); assert.equal(session.isLogin(), true); @@ -238,5 +223,18 @@ describe('plugin:cache', function() { assert.equal(session.isLogin(), false); done(); }); + + it('should logout ok', function(done) { + // before logout + cache.set(h.KEYS.user, USER); + assert.deepEqual(session.getUser(), USER); + assert.equal(session.isLogin(), true); + + // after logout + plugin.logout(null, true); + assert.equal(session.getUser(), null); + assert.equal(session.isLogin(), false); + done(); + }); }); // #user }); diff --git a/test/plugins/test_leetcode.js b/test/plugins/test_leetcode.js index ce29ccab..ef099b9b 100644 --- a/test/plugins/test_leetcode.js +++ b/test/plugins/test_leetcode.js @@ -1,17 +1,19 @@ -var _ = require('underscore'); -var assert = require('chai').assert; -var nock = require('nock'); -var rewire = require('rewire'); +'use strict'; +const _ = require('underscore'); +const assert = require('chai').assert; +const nock = require('nock'); +const rewire = require('rewire'); -var config = require('../../lib/config'); -var log = require('../../lib/log'); +const config = require('../../lib/config'); +const chalk = require('../../lib/chalk'); +const log = require('../../lib/log'); -var plugin = rewire('../../lib/plugins/leetcode'); -var session = rewire('../../lib/session'); +const plugin = rewire('../../lib/plugins/leetcode'); +const session = rewire('../../lib/session'); describe('plugin:leetcode', function() { - var USER = {hash: 'abcdef'}; - var PROBLEM = { + const USER = {hash: 'abcdef'}; + const PROBLEM = { id: 389, name: 'Find the Difference', slug: 'find-the-difference', @@ -19,7 +21,7 @@ describe('plugin:leetcode', function() { locked: false, file: '/dev/null' }; - var SUBMISSION = { + const SUBMISSION = { id: '73790064', lang: 'cpp', runtime: '9 ms', @@ -30,12 +32,11 @@ describe('plugin:leetcode', function() { before(function() { log.init(); config.init(); + chalk.init(); plugin.init(); - session.getUser = function() { - return USER; - }; - session.saveUser = function() {}; + session.getUser = () => USER; + session.saveUser = () => {}; plugin.__set__('session', session); }); @@ -43,8 +44,7 @@ describe('plugin:leetcode', function() { it('should ok', function(done) { nock('https://leetcode.com') .get('/accounts/login/') - .reply(200, '', { - 'Set-Cookie': [ + .reply(200, '', { 'Set-Cookie': [ 'csrftoken=LOGIN_CSRF_TOKEN; Max-Age=31449600; Path=/; secure' ]}); @@ -53,13 +53,17 @@ describe('plugin:leetcode', function() { .reply(302, '', { 'Set-Cookie': [ 'csrftoken=SESSION_CSRF_TOKEN; Max-Age=31449600; Path=/; secure', - 'LEETCODE_SESSION=SESSION_ID; Max-Age=31449600; Path=/; secure', - "messages='Successfully signed in as Eric.'; Max-Age=31449600; Path=/; secure" + 'LEETCODE_SESSION=SESSION_ID; Max-Age=31449600; Path=/; secure' ]}); nock('https://leetcode.com') .get('/list/api/questions') - .reply(200, JSON.stringify({favorites: {private_favorites: [{id_hash: 'abcdef', name: 'Favorite'}]}})); + .reply(200, JSON.stringify({ + user_name: 'Eric', + favorites: { + private_favorites: [{id_hash: 'abcdef', name: 'Favorite'}] + } + })); plugin.login({}, function(e, user) { assert.equal(e, null); @@ -158,7 +162,7 @@ describe('plugin:leetcode', function() { }); it('should fail if not login', function(done) { - config.AUTO_LOGIN = false; + config.autologin.enable = false; nock('https://leetcode.com') .get('/api/problems/algorithms/') .replyWithFile(200, './test/mock/problems.nologin.json.20161015'); @@ -171,15 +175,19 @@ describe('plugin:leetcode', function() { }); // #getCategoryProblems describe('#getProblem', function() { + beforeEach(function() { + PROBLEM.locked = false; + }); + it('should ok', function(done) { nock('https://leetcode.com') - .get('/problems/find-the-difference') - .replyWithFile(200, './test/mock/find-the-difference.html.20170714'); + .post('/graphql') + .replyWithFile(200, './test/mock/find-the-difference.json.20171216'); plugin.getProblem(PROBLEM, function(e, problem) { assert.equal(e, null); - assert.equal(problem.totalAC, '73.2K'); - assert.equal(problem.totalSubmit, '142K'); + assert.equal(problem.totalAC, '89.7K'); + assert.equal(problem.totalSubmit, '175.7K'); assert.equal(problem.desc, [ '', @@ -203,7 +211,7 @@ describe('plugin:leetcode', function() { '' ].join('\r\n')); - assert.equal(problem.templates.length, 11); + assert.equal(problem.templates.length, 12); assert.equal(problem.templates[0].value, 'cpp'); assert.equal(problem.templates[0].text, 'C++'); @@ -221,7 +229,7 @@ describe('plugin:leetcode', function() { assert.equal(problem.templates[1].text, 'Java'); assert.equal(problem.templates[1].defaultCode, [ - 'public class Solution {', + 'class Solution {', ' public char findTheDifference(String s, String t) {', ' ', ' }', @@ -333,15 +341,23 @@ describe('plugin:leetcode', function() { '}' ].join('\n')); + assert.equal(problem.templates[11].value, 'kotlin'); + assert.equal(problem.templates[11].text, 'Kotlin'); + assert.equal(problem.templates[11].defaultCode, + [ + 'class Solution {', + ' fun findTheDifference(s: String, t: String): Char {', + ' ', + ' }', + '}' + ].join('\n')); + done(); }); }); it('should fail if no permission for locked', function(done) { PROBLEM.locked = true; - nock('https://leetcode.com') - .get('/problems/find-the-difference') - .replyWithFile(200, './test/mock/locked.html.20161015'); plugin.getProblem(PROBLEM, function(e, problem) { assert.equal(e, 'failed to load locked problem!'); @@ -350,9 +366,7 @@ describe('plugin:leetcode', function() { }); it('should fail if session expired', function(done) { - nock('https://leetcode.com') - .get('/problems/find-the-difference') - .reply(403); + nock('https://leetcode.com').post('/graphql').reply(403); plugin.getProblem(PROBLEM, function(e, problem) { assert.equal(e, session.errors.EXPIRED); @@ -361,9 +375,7 @@ describe('plugin:leetcode', function() { }); it('should fail if http error', function(done) { - nock('https://leetcode.com') - .get('/problems/find-the-difference') - .reply(500); + nock('https://leetcode.com').post('/graphql').reply(500); plugin.getProblem(PROBLEM, function(e, problem) { assert.deepEqual(e, {msg: 'http error', statusCode: 500}); @@ -372,9 +384,7 @@ describe('plugin:leetcode', function() { }); it('should fail if unknown error', function(done) { - nock('https://leetcode.com') - .get('/problems/find-the-difference') - .replyWithError('unknown error!'); + nock('https://leetcode.com').post('/graphql').replyWithError('unknown error!'); plugin.getProblem(PROBLEM, function(e, problem) { assert.equal(e.message, 'unknown error!'); @@ -438,8 +448,6 @@ describe('plugin:leetcode', function() { }); it('should ok after delay', function(done) { - this.timeout(5000); - nock('https://leetcode.com') .post('/problems/find-the-difference/submit/') .reply(200, '{"error": "You run code too soon"}'); @@ -460,7 +468,7 @@ describe('plugin:leetcode', function() { assert.equal(results[0].ok, true); done(); }); - }); + }).timeout(5000); it('should fail if server error', function(done) { nock('https://leetcode.com') @@ -528,7 +536,7 @@ describe('plugin:leetcode', function() { describe('#getSubmissions', function() { it('should ok', function(done) { - var problem = { + const problem = { id: 1, name: 'Two Sum', slug: 'two-sum', @@ -636,7 +644,7 @@ describe('plugin:leetcode', function() { plugin.getFavorites(function(e, favorites) { assert.equal(e, null); - var my = favorites.favorites.private_favorites; + const my = favorites.favorites.private_favorites; assert.equal(my.length, 1); assert.equal(my[0].name, 'Favorite'); assert.equal(my[0].id_hash, 'abcdefg'); @@ -644,4 +652,68 @@ describe('plugin:leetcode', function() { }); }); }); // #getFavorites + + describe('#session', function() { + const DATA = {sessions: []}; + + it('should getSessions ok', function(done) { + nock('https://leetcode.com') + .post('/session/') + .reply(200, JSON.stringify(DATA)); + + plugin.getSessions(function(e, sessions) { + assert.notExists(e); + assert.deepEqual(sessions, []); + done(); + }); + }); + + it('should activateSessions ok', function(done) { + nock('https://leetcode.com') + .put('/session/', {func: 'activate', target: 1}) + .reply(200, JSON.stringify(DATA)); + + plugin.activateSession({id: 1}, function(e, sessions) { + assert.notExists(e); + assert.deepEqual(sessions, []); + done(); + }); + }); + + it('should createSessions ok', function(done) { + nock('https://leetcode.com') + .put('/session/', {func: 'create', name: 's1'}) + .reply(200, JSON.stringify(DATA)); + + plugin.createSession('s1', function(e, sessions) { + assert.notExists(e); + assert.deepEqual(sessions, []); + done(); + }); + }); + + it('should deleteSessions ok', function(done) { + nock('https://leetcode.com') + .delete('/session/', {target: 1}) + .reply(200, JSON.stringify(DATA)); + + plugin.deleteSession({id: 1}, function(e, sessions) { + assert.notExists(e); + assert.deepEqual(sessions, []); + done(); + }); + }); + + it('should fail if 302 returned', function(done) { + nock('https://leetcode.com') + .post('/session/') + .reply(302); + + plugin.getSessions(function(e, sessions) { + assert.deepEqual(e, session.errors.EXPIRED); + assert.notExists(sessions); + done(); + }); + }); + }); // #session }); diff --git a/test/plugins/test_retry.js b/test/plugins/test_retry.js index 4a249ada..dbdb060c 100644 --- a/test/plugins/test_retry.js +++ b/test/plugins/test_retry.js @@ -1,25 +1,24 @@ -var assert = require('chai').assert; -var rewire = require('rewire'); +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); -var log = require('../../lib/log'); +const log = require('../../lib/log'); -var config = rewire('../../lib/config'); -var session = rewire('../../lib/session'); -var plugin = rewire('../../lib/plugins/retry'); +const config = rewire('../../lib/config'); +const session = rewire('../../lib/session'); +const plugin = rewire('../../lib/plugins/retry'); describe('plugin:retry', function() { - var USER = {}; - var NEXT = {}; - var PROBLEMS = [{id: 0, name: 'name0'}]; + const USER = {}; + const NEXT = {}; + const PROBLEMS = [{id: 0, name: 'name0'}]; before(function() { log.init(); config.init(); plugin.init(); - session.getUser = function() { - return USER; - }; + session.getUser = () => USER; plugin.__set__('config', config); plugin.__set__('session', session); @@ -27,10 +26,8 @@ describe('plugin:retry', function() { }); it('should fail if auto login disabled', function(done) { - config.AUTO_LOGIN = false; - NEXT.getProblems = function(cb) { - return cb(session.errors.EXPIRED); - }; + config.autologin.enable = false; + NEXT.getProblems = cb => cb(session.errors.EXPIRED); plugin.getProblems(function(e, problems) { assert.equal(e, session.errors.EXPIRED); @@ -38,53 +35,60 @@ describe('plugin:retry', function() { }); }); - it('should retry if session expired', function(done) { - config.AUTO_LOGIN = true; + it('should retry ok if finally ok', function(done) { + config.autologin.enable = true; + config.autologin.retry = 3; - var n = 0; + let n = 0; NEXT.getProblems = function(cb) { - ++n; - if (n === 1) return cb(session.errors.EXPIRED); - return cb(null, PROBLEMS); - }; - - NEXT.login = function(user, cb) { - return cb(null, user); + return ++n <= 3 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); }; + NEXT.login = (user, cb) => cb(null, user); plugin.getProblems(function(e, problems) { - assert.equal(e, null); + assert.notExists(e); assert.equal(problems, PROBLEMS); done(); }); }); - it('should fail if user expired locally', function(done) { - config.AUTO_LOGIN = true; + it('should retry fail if always failed', function(done) { + config.autologin.enable = true; + config.autologin.retry = 2; - var n = 0; + let n = 0; NEXT.getProblems = function(cb) { - ++n; - if (n === 1) return cb(session.errors.EXPIRED); - return cb(null, PROBLEMS); + return ++n <= 3 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); }; + NEXT.login = (user, cb) => { + return n == 1 ? cb(null, user) : cb('login failed'); + } - session.getUser = function() { - return null; + plugin.getProblems(function(e) { + assert.deepEqual(e, session.errors.EXPIRED); + done(); + }); + }); + + it('should fail if user expired locally', function(done) { + config.autologin.enable = true; + + let n = 0; + NEXT.getProblems = function(cb) { + return ++n === 1 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); }; + session.getUser = () => null; plugin.getProblems(function(e, problems) { - assert.equal(e, null); + assert.notExists(e); assert.equal(problems, PROBLEMS); done(); }); }); it('should fail if other errors', function(done) { - config.AUTO_LOGIN = true; - NEXT.getProblems = function(cb) { - return cb('unknown error'); - }; + config.autologin.enable = true; + NEXT.getProblems = cb => cb('unknown error'); plugin.getProblems(function(e, problems) { assert.equal(e, 'unknown error'); diff --git a/test/test_cache.js b/test/test_cache.js index 4e3405f9..caba14c1 100644 --- a/test/test_cache.js +++ b/test/test_cache.js @@ -1,60 +1,48 @@ -var execSync = require('child_process').execSync; +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); -var assert = require('chai').assert; -var rewire = require('rewire'); - -var cache = rewire('../lib/cache'); -var h = rewire('../lib/helper'); +const th = require('./helper'); describe('cache', function() { - var k = '.test'; - var v = {test: 'data'}; + let cache; - before(function() { - var cachedir = './tmp'; - execSync('rm -rf ' + cachedir); + const K = '.test'; + const V = {test: 'data'}; - h.getCacheDir = function() { - return cachedir; - }; - cache.__set__('h', h); - }); + beforeEach(function() { + th.clean(); - it('should ok when not cached', function() { - cache.del(k); + const file = rewire('../lib/file'); + file.cacheDir = () => th.DIR; - assert.equal(cache.get(k), null); - assert.equal(cache.del(k), false); + cache = rewire('../lib/cache'); + cache.__set__('file', file); + cache.init(); }); - it('should ok when cached', function() { - assert.equal(cache.set(k, v), true); + it('should get ok when not cached', function() { + cache.del(K); + assert.equal(cache.get(K), null); + assert.equal(cache.del(K), false); + }); - assert.deepEqual(cache.get(k), v); - assert.equal(cache.del(k), true); + it('should get ok when cached', function() { + assert.equal(cache.set(K, V), true); + assert.deepEqual(cache.get(K), V); + assert.equal(cache.del(K), true); }); it('should list ok when no cached', function() { - var items = cache.list(); + const items = cache.list(); assert.equal(items.length, 0); }); it('should list ok when cached', function() { - assert.equal(cache.set(k, v), true); - - var items = cache.list(); + assert.equal(cache.set(K, V), true); + const items = cache.list(); assert.equal(items.length, 1); - - assert.equal(items[0].name, k); - assert.equal(items[0].size, JSON.stringify(v).length); - }); - - it('should list ok when cache dir not exist', function() { - h.getCacheDir = function() { - return '/not-exist-dir'; - }; - - var items = cache.list(); - assert.equal(items.length, 0); + assert.equal(items[0].name, K); + assert.equal(items[0].size, JSON.stringify(V).length); }); }); diff --git a/test/test_chalk.js b/test/test_chalk.js new file mode 100644 index 00000000..b0c17089 --- /dev/null +++ b/test/test_chalk.js @@ -0,0 +1,91 @@ +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); + +// refer to https://en.wikipedia.org/wiki/ANSI_escape_code +describe('chalk', function() { + let chalk; + + beforeEach(function() { + chalk = rewire('../lib/chalk'); + chalk.enabled = true; + chalk.use256 = true; + chalk.use16m = false; + }); + + it('should ok w/ 256 colors', function() { + chalk.init(); + chalk.setTheme('default'); + + assert.equal(chalk.black(' '), '\u001b[38;5;16m \u001b[39m'); + assert.equal(chalk.red(' '), '\u001b[38;5;196m \u001b[39m'); + assert.equal(chalk.green(' '), '\u001b[38;5;46m \u001b[39m'); + assert.equal(chalk.yellow(' '), '\u001b[38;5;226m \u001b[39m'); + assert.equal(chalk.blue(' '), '\u001b[38;5;21m \u001b[39m'); + assert.equal(chalk.magenta(' '), '\u001b[38;5;201m \u001b[39m'); + assert.equal(chalk.cyan(' '), '\u001b[38;5;51m \u001b[39m'); + assert.equal(chalk.white(' '), '\u001b[38;5;231m \u001b[39m'); + + assert.equal(chalk.bold(' '), '\u001b[1m \u001b[22m'); + assert.equal(chalk.dim(' '), '\u001b[2m \u001b[22m'); + assert.equal(chalk.italic(' '), '\u001b[3m \u001b[23m'); + assert.equal(chalk.inverse(' '), '\u001b[7m \u001b[27m'); + assert.equal(chalk.strikethrough(' '), '\u001b[9m \u001b[29m'); + assert.equal(chalk.underline(' '), '\u001b[4m \u001b[24m'); + }); + + it('should ok w/ 8 colors', function() { + chalk.use256 = false; + chalk.init(); + chalk.setTheme('default'); + + assert.equal(chalk.black(' '), '\u001b[30m \u001b[39m'); + assert.equal(chalk.red(' '), '\u001b[91m \u001b[39m'); + assert.equal(chalk.green(' '), '\u001b[92m \u001b[39m'); + assert.equal(chalk.yellow(' '), '\u001b[93m \u001b[39m'); + assert.equal(chalk.blue(' '), '\u001b[94m \u001b[39m'); + assert.equal(chalk.magenta(' '), '\u001b[95m \u001b[39m'); + assert.equal(chalk.cyan(' '), '\u001b[96m \u001b[39m'); + assert.equal(chalk.white(' '), '\u001b[97m \u001b[39m'); + }); + + it('should ok w/o colors', function() { + chalk.enabled = false; + chalk.init(); + chalk.setTheme('default'); + + assert.equal(chalk.black(' '), ' '); + assert.equal(chalk.red(' '), ' '); + assert.equal(chalk.green(' '), ' '); + assert.equal(chalk.yellow(' '), ' '); + assert.equal(chalk.blue(' '), ' '); + assert.equal(chalk.magenta(' '), ' '); + assert.equal(chalk.cyan(' '), ' '); + assert.equal(chalk.white(' '), ' '); + }); + + it('should sprint w/ 256 colors ok', function() { + chalk.init(); + chalk.setTheme('default'); + assert.equal(chalk.sprint(' ', '#00ff00'), '\u001b[38;5;46m \u001b[39m'); + }); + + it('should sprint w/ 8 colors ok', function() { + chalk.use256 = false; + chalk.init(); + chalk.setTheme('default'); + assert.equal(chalk.sprint(' ', '#00ff00'), '\u001b[92m \u001b[39m'); + }); + + it('should set theme ok', function() { + chalk.init(); + chalk.setTheme('dark'); + assert.equal(chalk.sprint(' ', '#009900'), chalk.green(' ')); + }); + + it('should set unknown theme ok', function() { + chalk.init(); + chalk.setTheme('unknown'); + assert.equal(chalk.sprint(' ', '#00ff00'), chalk.green(' ')); + }); +}); diff --git a/test/test_config.js b/test/test_config.js index 3b345ceb..9ae828dd 100644 --- a/test/test_config.js +++ b/test/test_config.js @@ -1,51 +1,64 @@ -var assert = require('chai').assert; -var rewire = require('rewire'); -var _ = require('underscore'); +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); +const _ = require('underscore'); + +const th = require('./helper'); describe('config', function() { - it('should ok w/o local config', function() { - var h = rewire('../lib/helper'); - h.getConfigFile = function() { - return 'local-config-not-exist-at-all'; - }; + let config; + const FILE = './tmp/config.json'; + + beforeEach(function() { + th.clean(); + + const file = rewire('../lib/file'); + file.configFile = () => FILE; + + config = rewire('../lib/config'); + config.__set__('file', file); + }); + + function createConfigFile(data) { + const fs = require('fs'); + fs.writeFileSync(FILE, JSON.stringify(data)); + } - var config = rewire('../lib/config'); - config.__set__('h', h); + it('should ok w/o local config', function() { + const DEFAULT_CONFIG = config.__get__('DEFAULT_CONFIG'); config.init(); - var expect = config.getDefaultConfig(); - var actual = _.extendOwn({}, config); // remove 'init' function + let actual = config.getAll(); + let expect = DEFAULT_CONFIG; assert.deepEqual(actual, expect); - expect = config.getUserConfig(); - actual = config.__get__('DEFAULT_USER_CONFIG'); + actual = config.getAll(true); + expect = _.omit(expect, 'sys'); assert.deepEqual(actual, expect); }); it('should ok w/ local config', function() { - var localConfig = { - AUTO_LOGIN: false, - LANG: 'ruby', - USE_COLOR: false - }; - - var h = rewire('../lib/helper'); - h.getFileData = function() { - return JSON.stringify(localConfig); - }; - - var config = rewire('../lib/config'); - config.__set__('h', h); + createConfigFile({ + autologin: {enable: false}, + code: {lang: 'ruby'}, + color: {enable: false} + }); config.init(); - var expect = config.getDefaultConfig(); - var actual = _.extendOwn({}, config); // remove 'init' function - _.extendOwn(expect, localConfig); - assert.deepEqual(actual, expect); + assert.equal(config.autologin.enable, false); + assert.equal(config.code.lang, 'ruby'); + assert.equal(config.color.enable, false); + assert.equal(config.code.editor, 'vim'); + }); - expect = config.getUserConfig(); - actual = config.__get__('DEFAULT_USER_CONFIG'); - _.extendOwn(actual, localConfig); - assert.deepEqual(actual, expect); + it('should remove legacy keys', function() { + createConfigFile({ + USE_COLOR: true, + code: {lang: 'ruby'} + }); + config.init(); + + assert.equal(config.USE_COLOR, undefined); + assert.equal(config.code.lang, 'ruby'); }); }); diff --git a/test/test_core.js b/test/test_core.js index fdf36da7..0a436bb4 100644 --- a/test/test_core.js +++ b/test/test_core.js @@ -1,69 +1,140 @@ -var fs = require('fs'); - -var _ = require('underscore'); -var assert = require('chai').assert; -var rewire = require('rewire'); - -var log = require('../lib/log'); - -var session = rewire('../lib/session'); -var plugin = rewire('../lib/core'); +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); describe('core', function() { - var PROBLEMS = [ - {id: 0, name: 'name0', slug: 'slug0', starred: false, category: 'algorithms'}, - {id: 1, name: 'name1', slug: 'slug1', starred: true, category: 'algorithms'} + let core; + let next; + + const PROBLEMS = [ + { + category: 'algorithms', + id: 0, + fid: 0, + name: 'name0', + slug: 'slug0', + level: 'Hard', + locked: true, + starred: false, + state: 'ac', + tags: ['google', 'facebook'] + }, + { + category: 'algorithms', + companies: ['amazon', 'facebook'], + id: 1, + fid: 1, + name: 'name1', + slug: 'slug1', + level: 'Easy', + locked: false, + starred: true, + state: 'none' + } ]; - var USER = {}; - var NEXT = {}; before(function() { + const log = require('../lib/log'); log.init(); - - session.getUser = function() { - return USER; - }; - - plugin.__set__('session', session); - plugin.setNext(NEXT); }); beforeEach(function() { - NEXT.getProblems = function(cb) { - return cb(null, PROBLEMS); - }; - NEXT.getProblem = function(problem, cb) { - return cb(null, problem); - }; + next = {}; + next.getProblems = cb => cb(null, PROBLEMS); + next.getProblem = (p, cb) => cb(null, p); + + core = rewire('../lib/core'); + core.setNext(next); }); + describe('#filterProblems', function() { + it('should filter by query ok', function(done) { + const cases = [ + ['', [0, 1]], + ['x', [0, 1]], + ['h', [0]], + ['H', [1]], + ['m', []], + ['M', [0, 1]], + ['l', [0]], + ['L', [1]], + ['s', [1]], + ['S', [0]], + ['d', [0]], + ['D', [1]], + ['eLsD', [1]], + ['Dh', []] + ]; + let n = cases.length; + + for (let x of cases) { + core.filterProblems({query: x[0]}, function(e, problems) { + assert.notExists(e); + assert.equal(problems.length, x[1].length); + + for (let i = 0; i < problems.length; ++i) + assert.equal(problems[i], PROBLEMS[x[1][i]]); + if (--n === 0) done(); + }); + } + }); + + it('should filter by tag ok', function(done) { + const cases = [ + [[], [0, 1]], + [['facebook'], [0, 1]], + [['google'], [0]], + [['amazon'], [1]], + [['apple'], []], + ]; + let n = cases.length; + + for (let x of cases) { + core.filterProblems({tag: x[0]}, function(e, problems) { + assert.notExists(e); + assert.equal(problems.length, x[1].length); + + for (let i = 0; i < problems.length; ++i) + assert.equal(problems[i], PROBLEMS[x[1][i]]); + if (--n === 0) done(); + }); + } + }); + + it('should fail if getProblems error', function(done) { + next.getProblems = cb => cb('getProblems error'); + core.filterProblems({}, function(e) { + assert.equal(e, 'getProblems error'); + done(); + }); + }); + }); // #filterProblems + describe('#starProblem', function() { - it('should starProblem ok', function(done) { - NEXT.starProblem = function(problem, starred, cb) { - return cb(null, starred); - }; + it('should ok', function(done) { + next.starProblem = (p, starred, cb) => cb(null, starred); assert.equal(PROBLEMS[0].starred, false); - plugin.starProblem(PROBLEMS[0], true, function(e, starred) { - assert.equal(e, null); + core.starProblem(PROBLEMS[0], true, function(e, starred) { + assert.notExists(e); assert.equal(starred, true); done(); }); }); - it('should starProblem ok if already starred', function(done) { + it('should ok if already starred', function(done) { assert.equal(PROBLEMS[1].starred, true); - plugin.starProblem(PROBLEMS[1], true, function(e, starred) { - assert.equal(e, null); + core.starProblem(PROBLEMS[1], true, function(e, starred) { + assert.notExists(e); assert.equal(starred, true); done(); }); }); - it('should starProblem ok if already unstarred', function(done) { + it('should ok if already unstarred', function(done) { assert.equal(PROBLEMS[0].starred, false); - plugin.starProblem(PROBLEMS[0], false, function(e, starred) { - assert.equal(e, null); + core.starProblem(PROBLEMS[0], false, function(e, starred) { + assert.notExists(e); assert.equal(starred, false); done(); }); @@ -71,35 +142,81 @@ describe('core', function() { }); // #starProblem describe('#exportProblem', function() { - function injectVerify(expected, done) { - plugin.__set__('fs', { - writeFileSync: function(f, data) { - assert.equal(data, expected); - done(); - }, - readFileSync: fs.readFileSync - }); - } + let file; + + beforeEach(function() { + file = rewire('../lib/file'); + file.init(); + core.__set__('file', file); + }); - it('should ok w/ code only', function(done) { - var expected = [ + it('should codeonly ok', function() { + file.isWindows = () => false; + + const expected = [ + '/**', + ' * Definition for singly-linked list.', + ' * struct ListNode {', + ' * int val;', + ' * ListNode *next;', + ' * ListNode(int x) : val(x), next(NULL) {}', + ' * };', + ' */', 'class Solution {', 'public:', ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', - '', + ' ', ' }', - '};' + '};', + '' ].join('\n'); - injectVerify(expected, done); + const problem = require('./mock/add-two-numbers.20161015.json'); + const opts = { + lang: 'cpp', + code: problem.templates[0].defaultCode, + tpl: 'codeonly' + }; + assert.equal(core.exportProblem(problem, opts), expected); + }); + + it('should codeonly ok in windows', function() { + file.isWindows = () => true; + + const expected = [ + '/**', + ' * Definition for singly-linked list.', + ' * struct ListNode {', + ' * int val;', + ' * ListNode *next;', + ' * ListNode(int x) : val(x), next(NULL) {}', + ' * };', + ' */', + 'class Solution {', + 'public:', + ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', + ' ', + ' }', + '};', + '' + ].join('\r\n'); - var problem = require('./mock/add-two-numbers.20161015.json'); - plugin.exportProblem(problem, 'test.cpp', true); + const problem = require('./mock/add-two-numbers.20161015.json'); + const opts = { + lang: 'cpp', + code: problem.templates[0].defaultCode, + tpl: 'codeonly' + }; + assert.equal(core.exportProblem(problem, opts), expected); }); - it('should ok w/ detailed comments', function(done) { - var expected = [ + it('should detailed ok with cpp', function() { + file.isWindows = () => false; + + const expected = [ '/*', + ' * @lc app=leetcode id=2 lang=cpp', + ' *', ' * [2] Add Two Numbers', ' *', ' * https://leetcode.com/problems/add-two-numbers', @@ -117,23 +234,38 @@ describe('core', function() { ' * Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)', ' * Output: 7 -> 0 -> 8', ' */', + '/**', + ' * Definition for singly-linked list.', + ' * struct ListNode {', + ' * int val;', + ' * ListNode *next;', + ' * ListNode(int x) : val(x), next(NULL) {}', + ' * };', + ' */', 'class Solution {', 'public:', ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', - '', + ' ', ' }', '};', '' ].join('\n'); - injectVerify(expected, done); - - var problem = require('./mock/add-two-numbers.20161015.json'); - plugin.exportProblem(problem, 'test.cpp', false); + const problem = require('./mock/add-two-numbers.20161015.json'); + const opts = { + lang: 'cpp', + code: problem.templates[0].defaultCode, + tpl: 'detailed' + }; + assert.equal(core.exportProblem(problem, opts), expected); }); - it('should ok w/ detailed comments, 2nd', function(done) { - var expected = [ + it('should detailed ok with ruby', function() { + file.isWindows = () => false; + + const expected = [ + '#', + '# @lc app=leetcode id=2 lang=ruby', '#', '# [2] Add Two Numbers', '#', @@ -170,74 +302,62 @@ describe('core', function() { '' ].join('\n'); - injectVerify(expected, done); - - var problem = require('./mock/add-two-numbers.20161015.json'); + const problem = require('./mock/add-two-numbers.20161015.json'); problem.testcase = null; - problem.code = _.find(problem.templates, function(template) { - return template.value === 'ruby'; - }).defaultCode; - plugin.exportProblem(problem, 'test.rb', false); + const opts = { + lang: 'ruby', + code: problem.templates[6].defaultCode, + tpl: 'detailed' + }; + assert.equal(core.exportProblem(problem, opts), expected); }); }); // #exportProblem describe('#getProblem', function() { - it('should getProblem by id ok', function(done) { - plugin.getProblem(0, function(e, problem) { - assert.equal(e, null); + it('should get by id ok', function(done) { + core.getProblem(0, function(e, problem) { + assert.notExists(e); assert.deepEqual(problem, PROBLEMS[0]); done(); }); }); - it('should getProblem by key ok', function(done) { - plugin.getProblem('slug0', function(e, problem) { - assert.equal(e, null); + it('should get by key ok', function(done) { + core.getProblem('slug0', function(e, problem) { + assert.notExists(e); assert.deepEqual(problem, PROBLEMS[0]); done(); }); }); - it('should getProblem error if not found', function(done) { - plugin.getProblem(3, function(e, problem) { + it('should fail if not found', function(done) { + core.getProblem(3, function(e, problem) { assert.equal(e, 'Problem not found!'); done(); }); }); - it('should getProblem fail if client error', function(done) { - NEXT.getProblem = function(problem, cb) { - return cb('client getProblem error'); - }; + it('should fail if client error', function(done) { + next.getProblem = (problem, cb) => cb('client getProblem error'); - plugin.getProblem(0, function(e, problem) { + core.getProblem(0, function(e, problem) { assert.equal(e, 'client getProblem error'); done(); }); }); - it('should getProblem random ok', function(done) { - NEXT.getProblems = function(cb) { - return cb(null, [ - {id: 0, state: 'ac', locked: false}, - {id: 1, state: 'none', locked: true}, - {id: 2, state: 'none', locked: false} - ]); - }; - - plugin.getProblem(undefined, function(e, problem) { - assert.equal(e, null); - assert.equal(problem.id, 2); + it('should ok if problem is already there', function(done) { + core.getProblem(PROBLEMS[1], function(e, problem) { + assert.notExists(e); + assert.deepEqual(problem, PROBLEMS[1]); done(); }); }); - it('should getProblem fail if getProblems error', function(done) { - NEXT.getProblems = function(cb) { - return cb('getProblems error'); - }; + it('should fail if getProblems error', function(done) { + next.getProblems = cb => cb('getProblems error'); - plugin.getProblem(0, function(e, problem) { + core.getProblem(0, function(e, problem) { assert.equal(e, 'getProblems error'); done(); }); diff --git a/test/test_file.js b/test/test_file.js new file mode 100644 index 00000000..d458e83f --- /dev/null +++ b/test/test_file.js @@ -0,0 +1,163 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const assert = require('chai').assert; +const rewire = require('rewire'); + +const th = require('./helper'); + +describe('file', function() { + let file; + + beforeEach(function() { + file = rewire('../lib/file'); + }); + + describe('#dirAndFiles', function() { + const HOME = path.join(__dirname, '..'); + + it('should ok on linux', function() { + if (file.isWindows()) this.skip(); + process.env.HOME = '/home/skygragon'; + + assert.equal(file.userHomeDir(), '/home/skygragon'); + assert.equal(file.homeDir(), '/home/skygragon/.lc'); + assert.equal(file.cacheDir(), '/home/skygragon/.lc/leetcode/cache'); + assert.equal(file.cacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); + assert.equal(file.configFile(), '/home/skygragon/.lc/config.json'); + assert.equal(file.name('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); + }); + + it('should ok on windows', function() { + if (!file.isWindows()) this.skip(); + process.env.HOME = ''; + process.env.USERPROFILE = 'C:\\Users\\skygragon'; + assert.equal(file.userHomeDir(), 'C:\\Users\\skygragon'); + assert.equal(file.homeDir(), 'C:\\Users\\skygragon\\.lc'); + assert.equal(file.cacheDir(), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache'); + assert.equal(file.cacheFile('xxx'), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'); + assert.equal(file.configFile(), 'C:\\Users\\skygragon\\.lc\\config.json'); + assert.equal(file.name('C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'), 'xxx'); + }); + + it('should codeDir ok', function() { + assert.equal(file.codeDir(), HOME); + assert.equal(file.codeDir('.'), HOME); + assert.equal(file.codeDir('icons'), path.join(HOME, 'icons')); + assert.equal(file.codeDir('lib/plugins'), path.join(HOME, 'lib', 'plugins')); + }); + + it('should listCodeDir ok', function() { + const files = file.listCodeDir('lib/plugins'); + assert.equal(files.length, 3); + assert.equal(files[0].name, 'cache'); + assert.equal(files[1].name, 'leetcode'); + assert.equal(files[2].name, 'retry'); + }); + + it('should pluginFile ok', function() { + const expect = path.join(HOME, 'lib/plugins/cache.js'); + assert.equal(file.pluginFile('cache.js'), expect); + assert.equal(file.pluginFile('./cache.js'), expect); + assert.equal(file.pluginFile('https://github.com/skygragon/cache.js'), expect); + }); + + it('should data ok with missing file', function() { + assert.equal(file.data('non-exist'), null); + }); + }); // #dirAndFiles + + describe('#meta', function() { + it('should meta ok within file content', function() { + file.data = x => [ + '/ *', + ' * @lc app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + const meta = file.meta('dummy'); + assert.equal(meta.app, 'leetcode') + assert.equal(meta.id, '123'); + assert.equal(meta.lang, 'javascript'); + }); + + it('should meta ok with white space', function() { + file.data = x => [ + '/ *', + ' * @lc app=leetcode id=123\t \t lang=javascript\r', + ' * /' + ].join('\n'); + const meta = file.meta('dummy'); + assert.equal(meta.app, 'leetcode') + assert.equal(meta.id, '123'); + assert.equal(meta.lang, 'javascript'); + }); + + it('should meta ok within file name', function() { + file.data = x => [ + '/ *', + ' * no meta app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + const meta = file.meta('321.dummy.py'); + assert(!meta.app) + assert.equal(meta.id, '321'); + assert.equal(meta.lang, 'python'); + }); + + it('should meta ok within deprecated file name', function() { + file.data = x => [ + '/ *', + ' * no meta app=leetcode id=123 lang=javascript', + ' * /' + ].join('\n'); + + var meta = file.meta('111.dummy.py3'); + assert(!meta.app) + assert.equal(meta.id, '111'); + assert.equal(meta.lang, 'python3'); + + meta = file.meta('222.dummy.python3.py'); + assert(!meta.app) + assert.equal(meta.id, '222'); + assert.equal(meta.lang, 'python3'); + }); + + it('should fmt ok', function() { + file.init(); + const data = file.fmt('${id}', {id: 123}); + assert.equal(data, '123'); + }); + }); // #meta + + describe('#genneral', function() { + beforeEach(function() { + th.clean(); + }); + afterEach(function() { + th.clean(); + }); + + it('should mkdir ok', function() { + const dir = th.DIR + 'dir'; + assert.equal(fs.existsSync(dir), false); + file.mkdir(dir); + assert.equal(fs.existsSync(dir), true); + file.mkdir(dir); + assert.equal(fs.existsSync(dir), true); + }); + + it('should mv ok', function() { + const SRC = th.Dir + 'src'; + const DST = th.DIR + 'dst'; + assert.equal(fs.existsSync(SRC), false); + assert.equal(fs.existsSync(DST), false); + file.mkdir(SRC); + assert.equal(fs.existsSync(SRC), true); + assert.equal(fs.existsSync(DST), false); + file.mv(SRC, DST); + assert.equal(fs.existsSync(SRC), false); + assert.equal(fs.existsSync(DST), true); + }); + }); // #general +}); diff --git a/test/test_helper.js b/test/test_helper.js index 7ca3bef2..143bda9e 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -1,11 +1,21 @@ -var assert = require('chai').assert; +'use strict'; +const assert = require('chai').assert; +const rewire = require('rewire'); +const _ = require('underscore'); -var chalk = require('../lib/chalk'); -var h = require('../lib/helper'); - -chalk.init(); +const chalk = require('../lib/chalk'); describe('helper', function() { + let h; + + before(function() { + chalk.init(); + }); + + beforeEach(function() { + h = rewire('../lib/helper'); + }); + describe('#prettyState', function() { it('should ok w/ color', function() { chalk.enabled = true; @@ -46,6 +56,20 @@ describe('helper', function() { }); }); // #prettyText + describe('#prettyLevel', function() { + it('should ok w/ color', function() { + chalk.enabled = true; + + assert.equal(h.prettyLevel('Easy'), chalk.green('Easy')); + assert.equal(h.prettyLevel('Medium'), chalk.yellow('Medium')); + assert.equal(h.prettyLevel('Hard'), chalk.red('Hard')); + assert.equal(h.prettyLevel('easy '), chalk.green('easy ')); + assert.equal(h.prettyLevel('medium'), chalk.yellow('medium')); + assert.equal(h.prettyLevel('hard '), chalk.red('hard ')); + assert.equal(h.prettyLevel('unknown'), 'unknown'); + }); + }); // #prettyLevel + describe('#prettySize', function() { it('should ok', function() { assert.equal(h.prettySize(0), '0.00B'); @@ -104,12 +128,13 @@ describe('helper', function() { assert.equal(h.langToExt('java'), '.java'); assert.equal(h.langToExt('javascript'), '.js'); assert.equal(h.langToExt('mysql'), '.sql'); + assert.equal(h.langToExt('php'), '.php'); assert.equal(h.langToExt('python'), '.py'); - assert.equal(h.langToExt('python3'), '.py3'); + assert.equal(h.langToExt('python3'), '.py'); assert.equal(h.langToExt('ruby'), '.rb'); + assert.equal(h.langToExt('rust'), '.rs'); assert.equal(h.langToExt('scala'), '.scala'); assert.equal(h.langToExt('swift'), '.swift'); - assert.equal(h.langToExt('rust'), '.raw'); }); }); // #langToExt @@ -122,20 +147,22 @@ describe('helper', function() { assert.equal(h.extToLang('../file.go'), 'golang'); assert.equal(h.extToLang('file.java'), 'java'); assert.equal(h.extToLang('c:/file.js'), 'javascript'); + assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); + assert.equal(h.extToLang('~/leetcode/hello.php'), 'php'); assert.equal(h.extToLang('c:/Users/skygragon/file.py'), 'python'); - assert.equal(h.extToLang('c:/Users/skygragon/file.py3'), 'python3'); assert.equal(h.extToLang('~/file.rb'), 'ruby'); + assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); assert.equal(h.extToLang('/tmp/file.scala'), 'scala'); assert.equal(h.extToLang('~/leetcode/file.swift'), 'swift'); - assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); assert.equal(h.extToLang('/home/skygragon/file.dat'), 'unknown'); }); }); // #extToLang describe('#langToCommentStyle', function() { it('should ok', function() { - var C_STYLE = {start: '/*', line: ' *', end: ' */'}; - var RUBY_STYLE = {start: '#', line: '#', end: '#'}; + const C_STYLE = {start: '/*', line: ' *', end: ' */'}; + const RUBY_STYLE = {start: '#', line: '#', end: '#'}; + const SQL_STYLE = {start: '--', line: '--', end: '--'}; assert.deepEqual(h.langToCommentStyle('bash'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('c'), C_STYLE); @@ -144,48 +171,26 @@ describe('helper', function() { assert.deepEqual(h.langToCommentStyle('golang'), C_STYLE); assert.deepEqual(h.langToCommentStyle('java'), C_STYLE); assert.deepEqual(h.langToCommentStyle('javascript'), C_STYLE); - assert.deepEqual(h.langToCommentStyle('mysql'), RUBY_STYLE); + assert.deepEqual(h.langToCommentStyle('mysql'), SQL_STYLE); + assert.deepEqual(h.langToCommentStyle('php'), C_STYLE); assert.deepEqual(h.langToCommentStyle('python'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('python3'), RUBY_STYLE); assert.deepEqual(h.langToCommentStyle('ruby'), RUBY_STYLE); + assert.deepEqual(h.langToCommentStyle('rust'), C_STYLE); assert.deepEqual(h.langToCommentStyle('scala'), C_STYLE); assert.deepEqual(h.langToCommentStyle('swift'), C_STYLE); }); }); // #langToCommentStyle - describe('#dirAndFiles', function() { - it('should ok', function() { - process.env.HOME = '/home/skygragon'; - - assert.equal(h.getHomeDir(), '/home/skygragon'); - assert.equal(h.getCacheDir(), '/home/skygragon/.lc'); - assert.equal(h.getCacheFile('xxx'), '/home/skygragon/.lc/xxx.json'); - assert.equal(h.getConfigFile(), '/home/skygragon/.lcconfig'); - assert.equal(h.getFilename('/home/skygragon/.lc/xxx.json'), 'xxx'); - - process.env.HOME = ''; - process.env.USERPROFILE = 'C:\\Users\\skygragon'; - assert.equal(h.getHomeDir(), 'C:\\Users\\skygragon'); - }); - - it('should getDirData ok', function() { - var files = h.getDirData(['lib', 'plugins']); - assert.equal(files.length, 3); - assert.equal(files[0].name, 'cache'); - assert.equal(files[1].name, 'leetcode'); - assert.equal(files[2].name, 'retry'); - }); - }); // #dirAndFiles - describe('#getSetCookieValue', function() { it('should ok', function() { - var resp = { + const resp = { headers: {'set-cookie': [ 'key1=value1; path=/; Httponly', 'key2=value2; path=/; Httponly'] } }; - var respNoSetCookie = { + const respNoSetCookie = { headers: {} }; @@ -198,14 +203,14 @@ describe('helper', function() { describe('#printSafeHTTP', function() { it('should hide sensitive info', function() { - var raw = [ + const raw = [ "Cookie: 'xxxxxx'", "'X-CSRFToken': 'yyyyyy'", "'set-cookie': ['zzzzzz']" ].join('\r\n'); - var hide = [ - "Cookie: