')) { 47 | // do not hit problem without html tags in desc (always exists for presenting testcase) 48 | log.debug('cache discarded for being no longer valid: ' + k + '.json'); 49 | } else if (!['likes', 'dislikes'].every(p => p in _problem)) { 50 | // do not hit problem without likes & dislikes (logic will be improved in new lib) 51 | log.debug('cache discarded for being too old: ' + k + '.json'); 52 | } else { 53 | // cache hit 54 | log.debug('cache hit: ' + k + '.json'); 55 | _.extendOwn(problem, _problem); 56 | return cb(null, problem); 57 | } 58 | } 59 | 60 | plugin.next.getProblem(problem, needTranslation, function(e, _problem) { 61 | if (e) return cb(e); 62 | 63 | plugin.saveProblem(_problem); 64 | return cb(null, _problem); 65 | }); 66 | }; 67 | 68 | plugin.saveProblem = function(problem) { 69 | // it would be better to leave specific problem cache being user 70 | // independent, thus try to reuse existing cache as much as possible 71 | // after changing user. 72 | const _problem = _.omit(problem, ['locked', 'state', 'starred']); 73 | return cache.set(h.KEYS.problem(problem), _problem); 74 | }; 75 | 76 | plugin.updateProblem = function(problem, kv) { 77 | const problems = cache.get(h.KEYS.problems); 78 | if (!problems) return false; 79 | 80 | const _problem = problems.find(x => x.id === problem.id); 81 | if (!_problem) return false; 82 | 83 | _.extend(_problem, kv); 84 | return cache.set(h.KEYS.problems, problems); 85 | }; 86 | 87 | plugin.login = function(user, cb) { 88 | this.logout(user, false); 89 | plugin.next.login(user, function(e, user) { 90 | if (e) return cb(e); 91 | session.saveUser(user); 92 | return cb(null, user); 93 | }); 94 | }; 95 | 96 | plugin.logout = function(user, purge) { 97 | if (!user) user = session.getUser(); 98 | if (purge) session.deleteUser(); 99 | // NOTE: need invalidate any user related cache 100 | session.deleteCodingSession(); 101 | return user; 102 | }; 103 | 104 | module.exports = plugin; 105 | -------------------------------------------------------------------------------- /lib/plugins/leetcode.cn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | 4 | var config = require('../config'); 5 | var h = require('../helper'); 6 | var log = require('../log'); 7 | var Plugin = require('../plugin'); 8 | var session = require('../session'); 9 | 10 | // 11 | // [Usage] 12 | // 13 | // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md 14 | // 15 | var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25', 16 | 'Plugin to talk with leetcode-cn APIs.'); 17 | 18 | plugin.init = function() { 19 | config.app = 'leetcode.cn'; 20 | config.sys.urls.base = 'https://leetcode.cn'; 21 | config.sys.urls.login = 'https://leetcode.cn/accounts/login/'; 22 | config.sys.urls.problems = 'https://leetcode.cn/api/problems/$category/'; 23 | config.sys.urls.problem = 'https://leetcode.cn/problems/$slug/description/'; 24 | config.sys.urls.graphql = 'https://leetcode.cn/graphql'; 25 | config.sys.urls.problem_detail = 'https://leetcode.cn/graphql'; 26 | config.sys.urls.test = 'https://leetcode.cn/problems/$slug/interpret_solution/'; 27 | config.sys.urls.session = 'https://leetcode.cn/session/'; 28 | config.sys.urls.submit = 'https://leetcode.cn/problems/$slug/submit/'; 29 | config.sys.urls.submissions = 'https://leetcode.cn/api/submissions/$slug'; 30 | config.sys.urls.submission = 'https://leetcode.cn/submissions/detail/$id/'; 31 | config.sys.urls.verify = 'https://leetcode.cn/submissions/detail/$id/check/'; 32 | config.sys.urls.favorites = 'https://leetcode.cn/list/api/questions'; 33 | config.sys.urls.favorite_delete = 'https://leetcode.cn/list/api/questions/$hash/$id'; 34 | // third parties 35 | config.sys.urls.github_login = 'https://leetcode.cn/accounts/github/login/?next=%2F'; 36 | config.sys.urls.linkedin_login = 'https://leetcode.cn/accounts/linkedin_oauth2/login/?next=%2F'; 37 | config.sys.urls.leetcode_redirect = 'https://leetcode.cn/'; 38 | }; 39 | 40 | // FIXME: refactor those 41 | // update options with user credentials 42 | function signOpts(opts, user) { 43 | opts.headers.Cookie = 'LEETCODE_SESSION=' + user.sessionId + 44 | ';csrftoken=' + user.sessionCSRF + ';'; 45 | opts.headers['X-CSRFToken'] = user.sessionCSRF; 46 | opts.headers['X-Requested-With'] = 'XMLHttpRequest'; 47 | } 48 | 49 | function makeOpts(url) { 50 | const opts = {}; 51 | opts.url = url; 52 | opts.headers = {}; 53 | 54 | if (session.isLogin()) 55 | signOpts(opts, session.getUser()); 56 | return opts; 57 | } 58 | 59 | function checkError(e, resp, expectedStatus) { 60 | if (!e && resp && resp.statusCode !== expectedStatus) { 61 | const code = resp.statusCode; 62 | log.debug('http error: ' + code); 63 | 64 | if (code === 403 || code === 401) { 65 | e = session.errors.EXPIRED; 66 | } else { 67 | e = {msg: 'http error', statusCode: code}; 68 | } 69 | } 70 | return e; 71 | } 72 | 73 | // overloading getProblems here to make sure everything related 74 | // to listing out problems can have a chance to be translated. 75 | // NOTE: Details of the problem is translated inside leetcode.js 76 | plugin.getProblems = function (needTranslation, cb) { 77 | plugin.next.getProblems(needTranslation, function(e, problems) { 78 | if (e) return cb(e); 79 | 80 | if (needTranslation) { 81 | // only translate titles of the list if user requested 82 | plugin.getProblemsTitle(function (e, titles) { 83 | if (e) return cb(e); 84 | 85 | problems.forEach(function (problem) { 86 | const title = titles[problem.id]; 87 | if (title) 88 | problem.name = title; 89 | }); 90 | 91 | return cb(null, problems); 92 | }); 93 | } else { 94 | return cb(null, problems); 95 | } 96 | }); 97 | }; 98 | 99 | plugin.getProblemsTitle = function(cb) { 100 | log.debug('running leetcode.cn.getProblemNames'); 101 | 102 | const opts = makeOpts(config.sys.urls.graphql); 103 | opts.headers.Origin = config.sys.urls.base; 104 | opts.headers.Referer = 'https://leetcode.cn/api/problems/algorithms/'; 105 | 106 | opts.json = true; 107 | opts.body = { 108 | query: [ 109 | 'query getQuestionTranslation($lang: String) {', 110 | ' translations: allAppliedQuestionTranslations(lang: $lang) {', 111 | ' title', 112 | ' questionId', 113 | ' __typename', 114 | ' }', 115 | '}' 116 | ].join('\n'), 117 | variables: {}, 118 | operationName: 'getQuestionTranslation' 119 | }; 120 | 121 | const spin = h.spin('Downloading questions titles'); 122 | request.post(opts, function(e, resp, body) { 123 | spin.stop(); 124 | e = checkError(e, resp, 200); 125 | if (e) return cb(e); 126 | 127 | const titles = []; 128 | body.data.translations.forEach(function(x) { 129 | titles[x.questionId] = x.title; 130 | }); 131 | 132 | return cb(null, titles); 133 | }); 134 | }; 135 | 136 | module.exports = plugin; 137 | -------------------------------------------------------------------------------- /lib/plugins/retry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('../config'); 3 | var log = require('../log'); 4 | var Plugin = require('../plugin'); 5 | var session = require('../session'); 6 | 7 | var plugin = new Plugin(30, 'retry', '', 8 | 'Plugin to retry last failed request if autologin.enable is on.'); 9 | 10 | const count = {}; 11 | 12 | function canRetry(e, name) { 13 | return config.autologin.enable && 14 | (e === session.errors.EXPIRED) && 15 | (count[name] || 0) < config.autologin.retry; 16 | } 17 | 18 | plugin.init = function() { 19 | const names = [ 20 | 'activateSession', 21 | 'createSession', 22 | 'deleteSession', 23 | 'getProblems', 24 | 'getProblem', 25 | 'getSessions', 26 | 'getSubmissions', 27 | 'getSubmission', 28 | 'getFavorites', 29 | 'testProblem', 30 | 'submitProblem', 31 | 'starProblem' 32 | ]; 33 | 34 | for (let name of names) { 35 | count[name] = 0; 36 | plugin[name] = function() { 37 | const args = Array.from(arguments); 38 | const cb = args.pop(); 39 | 40 | const _cb = function() { 41 | const results = Array.from(arguments); 42 | const e = results[0]; 43 | if (!canRetry(e, name)) { 44 | count[name] = 0; 45 | return cb.apply(null, results); 46 | } 47 | 48 | ++count[name]; 49 | plugin.relogin(function() { 50 | // for now we don't care result, just blindly retry 51 | plugin[name].apply(plugin, args.concat(cb)); 52 | }); 53 | }; 54 | 55 | const next = plugin.next; 56 | next[name].apply(next, args.concat(_cb)); 57 | }; 58 | } 59 | }; 60 | 61 | // leetcode.com is limiting one session alive in the same time, 62 | // which means once you login on web, your cli session will get 63 | // expired immediately. In that case we will try to re-login in 64 | // the backend to give a seamless user experience. 65 | plugin.relogin = function(cb) { 66 | log.debug('session expired, try to re-login...'); 67 | 68 | const user = session.getUser(); 69 | if (!user) { 70 | log.debug('relogin failed: no user found, please login again'); 71 | return cb(); 72 | } 73 | 74 | this.login(user, function(e) { 75 | if (e) { 76 | log.debug('login failed:' + e.msg); 77 | } else { 78 | log.debug('login successfully, cont\'d...'); 79 | } 80 | return cb(); 81 | }); 82 | }; 83 | 84 | module.exports = plugin; 85 | -------------------------------------------------------------------------------- /lib/plugins/solution.discuss.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | 3 | var log = require('../log'); 4 | var chalk = require('../chalk'); 5 | var Plugin = require('../plugin'); 6 | var session = require('../session'); 7 | 8 | // 9 | // [Usage] 10 | // 11 | // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/solution.discuss.md 12 | // 13 | var plugin = new Plugin(200, 'solution.discuss', '2019.02.03', 14 | 'Plugin to fetch most voted solution in discussions.'); 15 | 16 | var URL_DISCUSSES = 'https://leetcode.com/graphql'; 17 | var URL_DISCUSS = 'https://leetcode.com/problems/$slug/discuss/$id'; 18 | 19 | function getSolution(problem, lang, cb) { 20 | if (!problem) return cb(); 21 | 22 | if (lang === 'python3') lang = 'python'; 23 | 24 | var opts = { 25 | url: URL_DISCUSSES, 26 | json: true, 27 | body: { 28 | query: [ 29 | 'query questionTopicsList($questionId: String!, $orderBy: TopicSortingOption, $skip: Int, $query: String, $first: Int!, $tags: [String!]) {', 30 | ' questionTopicsList(questionId: $questionId, orderBy: $orderBy, skip: $skip, query: $query, first: $first, tags: $tags) {', 31 | ' ...TopicsList', 32 | ' }', 33 | '}', 34 | 'fragment TopicsList on TopicConnection {', 35 | ' totalNum', 36 | ' edges {', 37 | ' node {', 38 | ' id', 39 | ' title', 40 | ' post {', 41 | ' content', 42 | ' voteCount', 43 | ' author {', 44 | ' username', 45 | ' }', 46 | ' }', 47 | ' }', 48 | ' }', 49 | '}' 50 | ].join('\n'), 51 | 52 | operationName: 'questionTopicsList', 53 | variables: JSON.stringify({ 54 | query: '', 55 | first: 1, 56 | skip: 0, 57 | orderBy: 'most_votes', 58 | questionId: '' + problem.id, 59 | tags: [lang] 60 | }) 61 | } 62 | }; 63 | request(opts, function(e, resp, body) { 64 | if (e) return cb(e); 65 | if (resp.statusCode !== 200) 66 | return cb({msg: 'http error', statusCode: resp.statusCode}); 67 | 68 | const solutions = body.data.questionTopicsList.edges; 69 | const solution = solutions.length > 0 ? solutions[0].node : null; 70 | return cb(null, solution); 71 | }); 72 | } 73 | 74 | plugin.getProblem = function(problem, needTranslation, cb) { 75 | plugin.next.getProblem(problem, needTranslation, function(e, problem) { 76 | if (e || !session.argv.solution) return cb(e, problem); 77 | 78 | var lang = session.argv.lang; 79 | getSolution(problem, lang, function(e, solution) { 80 | if (e) return cb(e); 81 | if (!solution) return log.error('Solution not found for ' + lang); 82 | 83 | var link = URL_DISCUSS.replace('$slug', problem.slug).replace('$id', solution.id); 84 | var content = solution.post.content.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); 85 | 86 | log.info(); 87 | log.info(problem.name); 88 | log.info(); 89 | log.info(solution.title); 90 | log.info(); 91 | log.info(chalk.underline(link)); 92 | log.info(); 93 | log.info('* Lang: ' + lang); 94 | log.info('* Author: ' + solution.post.author.username); 95 | log.info('* Votes: ' + solution.post.voteCount); 96 | log.info(); 97 | log.info(content); 98 | }); 99 | }); 100 | }; 101 | 102 | module.exports = plugin; 103 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require('underscore'); 3 | 4 | var config = require('./config'); 5 | 6 | function Queue(tasks, ctx, onTask) { 7 | this.tasks = _.clone(tasks) || []; 8 | this.ctx = ctx || {}; 9 | this.onTask = onTask; 10 | this.error = null; 11 | } 12 | 13 | Queue.prototype.addTask = function(task) { 14 | this.tasks.push(task); 15 | return this; 16 | }; 17 | 18 | Queue.prototype.addTasks = function(tasks) { 19 | this.tasks = this.tasks.concat(tasks); 20 | return this; 21 | }; 22 | 23 | Queue.prototype.run = function(concurrency, onDone) { 24 | this.concurrency = concurrency || config.network.concurrency || 1; 25 | this.onDone = onDone; 26 | 27 | const self = this; 28 | for (let i = 0; i < this.concurrency; ++i) { 29 | setImmediate(function() { self.workerRun(); }); 30 | } 31 | }; 32 | 33 | Queue.prototype.workerRun = function() { 34 | // no more tasks, quit now 35 | if (this.tasks.length === 0) { 36 | if (--this.concurrency === 0 && this.onDone) 37 | this.onDone(this.error, this.ctx); 38 | return; 39 | } 40 | 41 | const task = this.tasks.shift(); 42 | const self = this; 43 | this.onTask(task, self, function(e) { 44 | if (e) self.error = e; 45 | 46 | // TODO: could retry failed task here. 47 | setImmediate(function() { self.workerRun(); }); 48 | }); 49 | }; 50 | 51 | module.exports = Queue; 52 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var moment = require('moment'); 3 | var _ = require('underscore'); 4 | 5 | var cache = require('./cache'); 6 | var config = require('./config'); 7 | var h = require('./helper'); 8 | 9 | const session = {}; 10 | 11 | session.errors = { 12 | EXPIRED: { 13 | msg: 'session expired, please login again', 14 | statusCode: -1 15 | } 16 | }; 17 | 18 | session.getUser = function() { 19 | return cache.get(h.KEYS.user); 20 | }; 21 | 22 | session.saveUser = function(user) { 23 | // when auto login enabled, have to save password to re-login later 24 | // otherwise don't dump password for the sake of security. 25 | const _user = _.omit(user, config.autologin.enable ? [] : ['pass']); 26 | cache.set(h.KEYS.user, _user); 27 | }; 28 | 29 | session.deleteUser = function() { 30 | cache.del(h.KEYS.user); 31 | }; 32 | 33 | session.deleteCodingSession = function() { 34 | cache.del(h.KEYS.problems); 35 | }; 36 | 37 | session.isLogin = function() { 38 | return this.getUser() !== null; 39 | }; 40 | 41 | session.updateStat = function(k, v) { 42 | // TODO: use other storage if too many stat data 43 | const today = moment().format('YYYY-MM-DD'); 44 | const stats = cache.get(h.KEYS.stat) || {}; 45 | const stat = stats[today] = stats[today] || {}; 46 | 47 | if (k.endsWith('.set')) { 48 | const s = new Set(stat[k] || []); 49 | s.add(v); 50 | stat[k] = Array.from(s); 51 | } else { 52 | stat[k] = (stat[k] || 0) + v; 53 | } 54 | 55 | cache.set(h.KEYS.stat, stats); 56 | }; 57 | 58 | module.exports = session; 59 | -------------------------------------------------------------------------------- /lib/sprintf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function len(s) { 4 | let s1 = s.replace(/\u001b\[[^m]*m/g, ''); // remove color controls 5 | s1 = s1.replace(/[^\x00-\xff]/g, ' '); // fix non-ascii 6 | return s1.length; 7 | } 8 | 9 | function padLeft(s, n, c) { 10 | let k = Math.max(0, n - len(s)); 11 | return c.repeat(k) + s; 12 | } 13 | 14 | function padRight(s, n , c) { 15 | let k = Math.max(0, n - len(s)); 16 | return s + c.repeat(k); 17 | } 18 | 19 | function padCenter(s, n, c) { 20 | let k = Math.max(0, n - len(s)); 21 | let r = (k - k % 2) / 2, l = k - r; 22 | return c.repeat(l) + s + c.repeat(r); 23 | } 24 | 25 | const tsprintf = function() { 26 | const args = Array.from(arguments); 27 | let fmt = args.shift(); 28 | return fmt.replace(/%[^s%]*[s%]/g, function(s) { 29 | if (s === '%%') return '%'; 30 | 31 | let x = '' + args.shift(); 32 | let n = 0; 33 | 34 | s = s.slice(1, s.length-1); 35 | if (s.length > 0) { 36 | switch (s[0]) { 37 | case '-': 38 | n = parseInt(s.slice(1)) || 0; 39 | x = padRight(x, n, ' '); 40 | break; 41 | case '=': 42 | n = parseInt(s.slice(1)) || 0; 43 | x = padCenter(x, n, ' '); 44 | break; 45 | case '0': 46 | n = parseInt(s.slice(1)) || 0; 47 | x = padLeft(x, n, '0'); 48 | break; 49 | default: 50 | n = parseInt(s) || 0; 51 | x = padLeft(x, n, ' '); 52 | break; 53 | } 54 | } 55 | 56 | return x; 57 | }); 58 | }; 59 | 60 | module.exports = tsprintf; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsc-leetcode-cli", 3 | "version": "2.8.0", 4 | "description": "A cli tool to enjoy leetcode!", 5 | "engines": { 6 | "node": ">=4" 7 | }, 8 | "bin": { 9 | "leetcode": "./bin/leetcode" 10 | }, 11 | "scripts": { 12 | "lint": "eslint lib/ test/", 13 | "test": "npm run lint && nyc mocha test test/plugins && nyc report --reporter=lcov", 14 | "travis": "node bin/pkg", 15 | "pkg": "pkg . --out-path=dist/ --targets" 16 | }, 17 | "pkg": { 18 | "scripts": [ 19 | "lib" 20 | ], 21 | "assets": [ 22 | "colors", 23 | "icons", 24 | "templates" 25 | ], 26 | "targets": [ 27 | "node10-linux-x64", 28 | "node10-macos-x64", 29 | "node10-win-x64" 30 | ] 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "http://github.com/skygragon/leetcode-cli.git" 35 | }, 36 | "keywords": [ 37 | "leetcode", 38 | "cli", 39 | "command", 40 | "tool" 41 | ], 42 | "author": { 43 | "name": "Eric Wang", 44 | "email": "skygragon@gmail.com" 45 | }, 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/leetcode-tools/leetcode-cli/issues" 49 | }, 50 | "homepage": "https://github.com/leetcode-tools/leetcode-cli#readme", 51 | "dependencies": { 52 | "ansi-styles": "3.2.1", 53 | "cheerio": "0.20.0", 54 | "he": "1.2.0", 55 | "mkdirp": "^1.0.4", 56 | "moment": "^2.29.1", 57 | "nconf": "^0.11.2", 58 | "ora": "3.0.0", 59 | "prompt": "1.0.0", 60 | "request": "2.88.0", 61 | "supports-color": "5.5.0", 62 | "underscore": "1.9.1", 63 | "wordwrap": "1.0.0", 64 | "yargs": "^15.4.1" 65 | }, 66 | "devDependencies": { 67 | "chai": "4.2.0", 68 | "eslint": "5.9.0", 69 | "eslint-config-google": "0.11.0", 70 | "mocha": "^8.3.2", 71 | "nock": "10.0.2", 72 | "nyc": "^15.1.0", 73 | "pkg": "^4.5.1", 74 | "rewire": "4.0.1" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /templates/codeonly.tpl: -------------------------------------------------------------------------------- 1 | ${comment.start} 2 | ${comment.line} @lc app=${app} id=${fid} lang=${lang} 3 | ${comment.line} 4 | ${comment.line} [${fid}] ${name} 5 | ${comment.end} 6 | 7 | ${comment.singleLine} @lc code=start 8 | ${code} 9 | ${comment.singleLine} @lc code=end 10 | -------------------------------------------------------------------------------- /templates/detailed.tpl: -------------------------------------------------------------------------------- 1 | ${comment.start} 2 | ${comment.line} @lc app=${app} id=${fid} lang=${lang} 3 | ${comment.line} 4 | ${comment.line} [${fid}] ${name} 5 | ${comment.line} 6 | ${comment.line} ${link} 7 | ${comment.line} 8 | ${comment.line} ${category} 9 | ${comment.line} ${level} (${percent}%) 10 | ${comment.line} Likes: ${likes} 11 | ${comment.line} Dislikes: ${dislikes} 12 | ${comment.line} Total Accepted: ${totalAC} 13 | ${comment.line} Total Submissions: ${totalSubmit} 14 | ${comment.line} Testcase Example: ${testcase} 15 | ${comment.line} 16 | {{ desc.forEach(function(x) { }}${comment.line} ${x} 17 | {{ }) }}${comment.end} 18 | 19 | ${comment.singleLine} @lc code=start 20 | ${code} 21 | ${comment.singleLine} @lc code=end 22 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | 'use_strict'; 2 | const fs = require('fs'); 3 | 4 | const h = { 5 | DIR: './tmp/' 6 | }; 7 | 8 | h.clean = function() { 9 | if (!fs.existsSync(this.DIR)) 10 | fs.mkdirSync(this.DIR); 11 | for (let f of fs.readdirSync(this.DIR)) { 12 | const fullpath = this.DIR + f; 13 | if (fs.statSync(fullpath).isDirectory()) 14 | fs.rmdirSync(fullpath); 15 | else 16 | fs.unlinkSync(fullpath); 17 | } 18 | }; 19 | 20 | module.exports = h; 21 | -------------------------------------------------------------------------------- /test/mock/add-two-numbers.20161015.json: -------------------------------------------------------------------------------- 1 | {"state":"ac","id":2,"category":"algorithms","name":"Add Two Numbers","key":"add-two-numbers","link":"https://leetcode.com/problems/add-two-numbers","locked":false,"percent":25.368142876074806,"level":"Medium","starred":true,"totalAC":"195263","totalSubmit":"769711","likes": "1","dislikes": "1","desc":"You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.\r\n\r\nInput: (2 -> 4 -> 3) + (5 -> 6 -> 4)\r\nOutput: 7 -> 0 -> 8","templates":[{"value":"cpp","text":"C++","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * struct ListNode {\r\n * int val;\r\n * ListNode *next;\r\n * ListNode(int x) : val(x), next(NULL) {}\r\n * };\r\n */\r\nclass Solution {\r\npublic:\r\n ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {\r\n \r\n }\r\n};"},{"value":"java","text":"Java","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * public class ListNode {\r\n * int val;\r\n * ListNode next;\r\n * ListNode(int x) { val = x; }\r\n * }\r\n */\r\npublic class Solution {\r\n public ListNode addTwoNumbers(ListNode l1, ListNode l2) {\r\n \r\n }\r\n}"},{"value":"python","text":"Python","defaultCode":"# Definition for singly-linked list.\r\n# class ListNode(object):\r\n# def __init__(self, x):\r\n# self.val = x\r\n# self.next = None\r\n\r\nclass Solution(object):\r\n def addTwoNumbers(self, l1, l2):\r\n \"\"\"\r\n :type l1: ListNode\r\n :type l2: ListNode\r\n :rtype: ListNode\r\n \"\"\"\r\n "},{"value":"c","text":"C","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * struct ListNode {\r\n * int val;\r\n * struct ListNode *next;\r\n * };\r\n */\r\nstruct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {\r\n \r\n}"},{"value":"csharp","text":"C#","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * public class ListNode {\r\n * public int val;\r\n * public ListNode next;\r\n * public ListNode(int x) { val = x; }\r\n * }\r\n */\r\npublic class Solution {\r\n public ListNode AddTwoNumbers(ListNode l1, ListNode l2) {\r\n \r\n }\r\n}"},{"value":"javascript","text":"JavaScript","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * function ListNode(val) {\r\n * this.val = val;\r\n * this.next = null;\r\n * }\r\n */\r\n/**\r\n * @param {ListNode} l1\r\n * @param {ListNode} l2\r\n * @return {ListNode}\r\n */\r\nvar addTwoNumbers = function(l1, l2) {\r\n \r\n};"},{"value":"ruby","text":"Ruby","defaultCode":"# Definition for singly-linked list.\r\n# class ListNode\r\n# attr_accessor :val, :next\r\n# def initialize(val)\r\n# @val = val\r\n# @next = nil\r\n# end\r\n# end\r\n\r\n# @param {ListNode} l1\r\n# @param {ListNode} l2\r\n# @return {ListNode}\r\ndef add_two_numbers(l1, l2)\r\n \r\nend"},{"value":"swift","text":"Swift","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * public class ListNode {\r\n * public var val: Int\r\n * public var next: ListNode?\r\n * public init(_ val: Int) {\r\n * self.val = val\r\n * self.next = nil\r\n * }\r\n * }\r\n */\r\nclass Solution {\r\n func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {\r\n \r\n }\r\n}"},{"value":"golang","text":"Go","defaultCode":"/**\r\n * Definition for singly-linked list.\r\n * type ListNode struct {\r\n * Val int\r\n * Next *ListNode\r\n * }\r\n */\r\nfunc addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {\r\n \r\n}"}],"testcase":"[2,4,3]\n[5,6,4]","testable":true} 2 | -------------------------------------------------------------------------------- /test/mock/find-the-difference-star.json.20200821: -------------------------------------------------------------------------------- 1 | {"data":{"addQuestionToFavorite":{"ok":true,"error":null,"favoriteIdHash":"","questionId":"389","__typename":"AddQuestionToFavorite"}}} -------------------------------------------------------------------------------- /test/mock/find-the-difference-unstar.json.20200821: -------------------------------------------------------------------------------- 1 | {"data":{"removeQuestionFromFavorite":{"ok":true,"error":null,"favoriteIdHash":"","questionId":"389","__typename":"RemoveQuestionFromFavorite"}}} -------------------------------------------------------------------------------- /test/mock/find-the-difference.json.20171216: -------------------------------------------------------------------------------- 1 | {"data":{"question":{"content":"\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"}}} -------------------------------------------------------------------------------- /test/mock/two-sum.submissions.json.20170425: -------------------------------------------------------------------------------- 1 | {"has_next":true,"submissions_dump":[{"lang":"cpp","time":"1 month, 3 weeks","status_display":"Accepted","runtime":"12 ms","url":"/submissions/detail/95464136/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 1 week","status_display":"Accepted","runtime":"13 ms","url":"/submissions/detail/78502271/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Accepted","runtime":"9 ms","url":"/submissions/detail/77791021/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Compile Error","runtime":"N/A","url":"/submissions/detail/77790928/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Accepted","runtime":"13 ms","url":"/submissions/detail/77685402/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Wrong Answer","runtime":"N/A","url":"/submissions/detail/77685362/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Time Limit Exceeded","runtime":"N/A","url":"/submissions/detail/77685329/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Runtime Error","runtime":"N/A","url":"/submissions/detail/77685279/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Compile Error","runtime":"N/A","url":"/submissions/detail/77685195/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Wrong Answer","runtime":"N/A","url":"/submissions/detail/77685140/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Wrong Answer","runtime":"N/A","url":"/submissions/detail/77684623/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Compile Error","runtime":"N/A","url":"/submissions/detail/77684584/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Compile Error","runtime":"N/A","url":"/submissions/detail/77684436/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Time Limit Exceeded","runtime":"N/A","url":"/submissions/detail/77684406/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Runtime Error","runtime":"N/A","url":"/submissions/detail/77684353/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Runtime Error","runtime":"N/A","url":"/submissions/detail/77683773/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Runtime Error","runtime":"N/A","url":"/submissions/detail/77683680/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Runtime Error","runtime":"N/A","url":"/submissions/detail/77683411/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Compile Error","runtime":"N/A","url":"/submissions/detail/77683347/","is_pending":false,"title":"Two Sum"},{"lang":"cpp","time":"6 months, 2 weeks","status_display":"Time Limit Exceeded","runtime":"N/A","url":"/submissions/detail/77682978/","is_pending":false,"title":"Two Sum"}]} 2 | -------------------------------------------------------------------------------- /test/plugins/test_cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('underscore'); 3 | const assert = require('chai').assert; 4 | const rewire = require('rewire'); 5 | 6 | const h = require('../../lib/helper'); 7 | const log = require('../../lib/log'); 8 | const config = require('../../lib/config'); 9 | const th = require('../helper'); 10 | 11 | describe('plugin:cache', function() { 12 | let plugin; 13 | let next; 14 | let cache; 15 | let file; 16 | let session; 17 | 18 | const PROBLEMS = [ 19 | {id: 0, fid: 0, name: 'name0', slug: 'slug0', starred: false, desc: '', likes: '1', dislikes: '1', category: 'algorithms'}, 20 | {id: 1, fid: 1, name: 'name1', slug: 'slug1', starred: true, desc: '', likes: '1', dislikes: '1', category: 'algorithms'} 21 | ]; 22 | const TRANSLATION_CONFIGS = { useEndpointTranslation: false }; 23 | const PROBLEM = {id: 0, fid: 0, slug: 'slug0', category: 'algorithms'}; 24 | 25 | before(function() { 26 | log.init(); 27 | config.init(); 28 | }); 29 | 30 | beforeEach(function() { 31 | th.clean(); 32 | next = {}; 33 | 34 | file = rewire('../../lib/file'); 35 | file.cacheDir = () => th.DIR; 36 | 37 | cache = rewire('../../lib/cache'); 38 | cache.__set__('file', file); 39 | cache.init(); 40 | 41 | session = rewire('../../lib/session'); 42 | session.__set__('cache', cache); 43 | 44 | plugin = rewire('../../lib/plugins/cache'); 45 | plugin.__set__('cache', cache); 46 | plugin.__set__('session', session); 47 | plugin.init(); 48 | 49 | plugin.setNext(next); 50 | }); 51 | 52 | describe('#getProblems', function() { 53 | it('should getProblems w/ cache ok', function(done) { 54 | cache.set('problems', PROBLEMS); 55 | cache.set(h.KEYS.translation, TRANSLATION_CONFIGS); 56 | 57 | plugin.getProblems(false, function(e, problems) { 58 | assert.equal(e, null); 59 | assert.deepEqual(problems, PROBLEMS); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('should getProblems w/o cache ok', function(done) { 65 | cache.del('problems'); 66 | next.getProblems = (needT, cb) => cb(null, PROBLEMS); 67 | 68 | plugin.getProblems(false, function(e, problems) { 69 | assert.equal(e, null); 70 | assert.deepEqual(problems, PROBLEMS); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('should getProblems w/o cache fail if client error', function(done) { 76 | cache.del('problems'); 77 | next.getProblems = (needT, cb) => cb('client getProblems error'); 78 | 79 | plugin.getProblems(false, function(e, problems) { 80 | assert.equal(e, 'client getProblems error'); 81 | done(); 82 | }); 83 | }); 84 | }); // #getProblems 85 | 86 | describe('#getProblem', function() { 87 | it('should getProblem w/ cache ok', function(done) { 88 | cache.set('problems', PROBLEMS); 89 | cache.set(h.KEYS.translation, TRANSLATION_CONFIGS); 90 | cache.set('0.slug0.algorithms', PROBLEMS[0]); 91 | 92 | plugin.getProblem(_.clone(PROBLEM), false, function(e, problem) { 93 | assert.equal(e, null); 94 | assert.deepEqual(problem, PROBLEMS[0]); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should getProblem w/o cache ok', function(done) { 100 | cache.set('problems', PROBLEMS); 101 | cache.del('0.slug0.algorithms'); 102 | next.getProblem = (problem, needT, cb) => cb(null, PROBLEMS[0]); 103 | 104 | plugin.getProblem(_.clone(PROBLEM), false, function(e, problem) { 105 | assert.equal(e, null); 106 | assert.deepEqual(problem, PROBLEMS[0]); 107 | done(); 108 | }); 109 | }); 110 | 111 | it('should getProblem fail if client error', function(done) { 112 | cache.set('problems', PROBLEMS); 113 | cache.del('0.slug0.algorithms'); 114 | next.getProblem = (problem, needT, cb) => cb('client getProblem error'); 115 | 116 | plugin.getProblem(_.clone(PROBLEM), false, function(e, problem) { 117 | assert.equal(e, 'client getProblem error'); 118 | done(); 119 | }); 120 | }); 121 | }); // #getProblem 122 | 123 | describe('#saveProblem', function() { 124 | it('should ok', function() { 125 | cache.del('0.slug0.algorithms'); 126 | 127 | const problem = _.clone(PROBLEMS[0]); 128 | problem.locked = true; 129 | problem.state = 'ac'; 130 | 131 | const ret = plugin.saveProblem(problem); 132 | assert.equal(ret, true); 133 | assert.deepEqual(cache.get('0.slug0.algorithms'), 134 | {id: 0, fid: 0, slug: 'slug0', name: 'name0', desc: '', likes: '1', dislikes: '1', category: 'algorithms'}); 135 | }); 136 | }); // #saveProblem 137 | 138 | describe('#updateProblem', function() { 139 | it('should updateProblem ok', function(done) { 140 | cache.set('problems', PROBLEMS); 141 | cache.set(h.KEYS.translation, TRANSLATION_CONFIGS); 142 | 143 | const kv = {value: 'value00'}; 144 | const ret = plugin.updateProblem(PROBLEMS[0], kv); 145 | assert.equal(ret, true); 146 | 147 | plugin.getProblems(false, function(e, problems) { 148 | assert.equal(e, null); 149 | assert.deepEqual(problems, [ 150 | {id: 0, fid: 0, name: 'name0', slug: 'slug0', value: 'value00', starred: false, desc: '', likes: '1', dislikes: '1', category: 'algorithms'}, 151 | {id: 1, fid: 1, name: 'name1', slug: 'slug1', starred: true, desc: '', likes: '1', dislikes: '1', category: 'algorithms'} 152 | ]); 153 | done(); 154 | }); 155 | }); 156 | 157 | it('should updateProblem fail if no problems found', function() { 158 | cache.del('problems'); 159 | const ret = plugin.updateProblem(PROBLEMS[0], {}); 160 | assert.equal(ret, false); 161 | }); 162 | 163 | it('should updateProblem fail if unknown problem', function() { 164 | cache.set('problems', [PROBLEMS[1]]); 165 | const ret = plugin.updateProblem(PROBLEMS[0], {}); 166 | assert.equal(ret, false); 167 | }); 168 | }); // #updateProblem 169 | 170 | describe('#user', function() { 171 | const USER = {name: 'test-user', pass: 'password'}; 172 | const USER_SAFE = {name: 'test-user'}; 173 | 174 | it('should login ok', function(done) { 175 | config.autologin.enable = true; 176 | // before login 177 | cache.del(h.KEYS.user); 178 | assert.equal(session.getUser(), null); 179 | assert.equal(session.isLogin(), false); 180 | 181 | next.login = (user, cb) => cb(null, user); 182 | 183 | plugin.login(USER, function(e, user) { 184 | assert.equal(e, null); 185 | assert.deepEqual(user, USER); 186 | 187 | // after login 188 | assert.deepEqual(session.getUser(), USER); 189 | assert.equal(session.isLogin(), true); 190 | done(); 191 | }); 192 | }); 193 | 194 | it('should login ok w/ auto login', function(done) { 195 | config.autologin.enable = false; 196 | cache.del(h.KEYS.user); 197 | 198 | next.login = (user, cb) => cb(null, user); 199 | 200 | plugin.login(USER, function(e, user) { 201 | assert.equal(e, null); 202 | assert.deepEqual(user, USER); 203 | assert.deepEqual(session.getUser(), USER_SAFE); 204 | assert.equal(session.isLogin(), true); 205 | done(); 206 | }); 207 | }); 208 | 209 | it('should login fail if client login error', function(done) { 210 | next.login = (user, cb) => cb('client login error'); 211 | 212 | plugin.login(USER, function(e, user) { 213 | assert.equal(e, 'client login error'); 214 | done(); 215 | }); 216 | }); 217 | 218 | it('should logout ok', function(done) { 219 | // before logout 220 | cache.set(h.KEYS.user, USER); 221 | assert.deepEqual(session.getUser(), USER); 222 | assert.equal(session.isLogin(), true); 223 | 224 | // after logout 225 | plugin.logout(USER, true); 226 | assert.equal(session.getUser(), null); 227 | assert.equal(session.isLogin(), false); 228 | done(); 229 | }); 230 | 231 | it('should logout ok', function(done) { 232 | // before logout 233 | cache.set(h.KEYS.user, USER); 234 | assert.deepEqual(session.getUser(), USER); 235 | assert.equal(session.isLogin(), true); 236 | 237 | // after logout 238 | plugin.logout(null, true); 239 | assert.equal(session.getUser(), null); 240 | assert.equal(session.isLogin(), false); 241 | done(); 242 | }); 243 | }); // #user 244 | }); 245 | -------------------------------------------------------------------------------- /test/plugins/test_retry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | const log = require('../../lib/log'); 6 | 7 | const config = rewire('../../lib/config'); 8 | const session = rewire('../../lib/session'); 9 | const plugin = rewire('../../lib/plugins/retry'); 10 | 11 | describe('plugin:retry', function() { 12 | const USER = {}; 13 | const NEXT = {}; 14 | const PROBLEMS = [{id: 0, name: 'name0'}]; 15 | 16 | before(function() { 17 | log.init(); 18 | config.init(); 19 | plugin.init(); 20 | 21 | session.getUser = () => USER; 22 | 23 | plugin.__set__('config', config); 24 | plugin.__set__('session', session); 25 | plugin.setNext(NEXT); 26 | }); 27 | 28 | it('should fail if auto login disabled', function(done) { 29 | config.autologin.enable = false; 30 | NEXT.getProblems = cb => cb(session.errors.EXPIRED); 31 | 32 | plugin.getProblems(function(e, problems) { 33 | assert.equal(e, session.errors.EXPIRED); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should retry ok if finally ok', function(done) { 39 | config.autologin.enable = true; 40 | config.autologin.retry = 3; 41 | 42 | let n = 0; 43 | NEXT.getProblems = function(cb) { 44 | return ++n <= 3 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); 45 | }; 46 | NEXT.login = (user, cb) => cb(null, user); 47 | 48 | plugin.getProblems(function(e, problems) { 49 | assert.notExists(e); 50 | assert.equal(problems, PROBLEMS); 51 | done(); 52 | }); 53 | }); 54 | 55 | it('should retry fail if always failed', function(done) { 56 | config.autologin.enable = true; 57 | config.autologin.retry = 2; 58 | 59 | let n = 0; 60 | NEXT.getProblems = function(cb) { 61 | return ++n <= 3 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); 62 | }; 63 | NEXT.login = (user, cb) => { 64 | return n == 1 ? cb(null, user) : cb('login failed'); 65 | } 66 | 67 | plugin.getProblems(function(e) { 68 | assert.deepEqual(e, session.errors.EXPIRED); 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should fail if user expired locally', function(done) { 74 | config.autologin.enable = true; 75 | 76 | let n = 0; 77 | NEXT.getProblems = function(cb) { 78 | return ++n === 1 ? cb(session.errors.EXPIRED) : cb(null, PROBLEMS); 79 | }; 80 | session.getUser = () => null; 81 | 82 | plugin.getProblems(function(e, problems) { 83 | assert.notExists(e); 84 | assert.equal(problems, PROBLEMS); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('should fail if other errors', function(done) { 90 | config.autologin.enable = true; 91 | NEXT.getProblems = cb => cb('unknown error'); 92 | 93 | plugin.getProblems(function(e, problems) { 94 | assert.equal(e, 'unknown error'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/test_cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | const th = require('./helper'); 6 | 7 | describe('cache', function() { 8 | let cache; 9 | 10 | const K = '.test'; 11 | const V = {test: 'data'}; 12 | 13 | beforeEach(function() { 14 | th.clean(); 15 | 16 | const file = rewire('../lib/file'); 17 | file.cacheDir = () => th.DIR; 18 | 19 | cache = rewire('../lib/cache'); 20 | cache.__set__('file', file); 21 | cache.init(); 22 | }); 23 | 24 | it('should get ok when not cached', function() { 25 | cache.del(K); 26 | assert.equal(cache.get(K), null); 27 | assert.equal(cache.del(K), false); 28 | }); 29 | 30 | it('should get ok when cached', function() { 31 | assert.equal(cache.set(K, V), true); 32 | assert.deepEqual(cache.get(K), V); 33 | assert.equal(cache.del(K), true); 34 | }); 35 | 36 | it('should list ok when no cached', function() { 37 | const items = cache.list(); 38 | assert.equal(items.length, 0); 39 | }); 40 | 41 | it('should list ok when cached', function() { 42 | assert.equal(cache.set(K, V), true); 43 | const items = cache.list(); 44 | assert.equal(items.length, 1); 45 | assert.equal(items[0].name, K); 46 | assert.equal(items[0].size, JSON.stringify(V).length); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/test_chalk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | // refer to https://en.wikipedia.org/wiki/ANSI_escape_code 6 | describe('chalk', function() { 7 | let chalk; 8 | 9 | beforeEach(function() { 10 | chalk = rewire('../lib/chalk'); 11 | chalk.enabled = true; 12 | chalk.use256 = true; 13 | chalk.use16m = false; 14 | }); 15 | 16 | it('should ok w/ 256 colors', function() { 17 | chalk.init(); 18 | chalk.setTheme('default'); 19 | 20 | assert.equal(chalk.black(' '), '\u001b[38;5;16m \u001b[39m'); 21 | assert.equal(chalk.red(' '), '\u001b[38;5;196m \u001b[39m'); 22 | assert.equal(chalk.green(' '), '\u001b[38;5;46m \u001b[39m'); 23 | assert.equal(chalk.yellow(' '), '\u001b[38;5;226m \u001b[39m'); 24 | assert.equal(chalk.blue(' '), '\u001b[38;5;21m \u001b[39m'); 25 | assert.equal(chalk.magenta(' '), '\u001b[38;5;201m \u001b[39m'); 26 | assert.equal(chalk.cyan(' '), '\u001b[38;5;51m \u001b[39m'); 27 | assert.equal(chalk.white(' '), '\u001b[38;5;231m \u001b[39m'); 28 | 29 | assert.equal(chalk.bold(' '), '\u001b[1m \u001b[22m'); 30 | assert.equal(chalk.dim(' '), '\u001b[2m \u001b[22m'); 31 | assert.equal(chalk.italic(' '), '\u001b[3m \u001b[23m'); 32 | assert.equal(chalk.inverse(' '), '\u001b[7m \u001b[27m'); 33 | assert.equal(chalk.strikethrough(' '), '\u001b[9m \u001b[29m'); 34 | assert.equal(chalk.underline(' '), '\u001b[4m \u001b[24m'); 35 | }); 36 | 37 | it('should ok w/ 8 colors', function() { 38 | chalk.use256 = false; 39 | chalk.init(); 40 | chalk.setTheme('default'); 41 | 42 | assert.equal(chalk.black(' '), '\u001b[30m \u001b[39m'); 43 | assert.equal(chalk.red(' '), '\u001b[91m \u001b[39m'); 44 | assert.equal(chalk.green(' '), '\u001b[92m \u001b[39m'); 45 | assert.equal(chalk.yellow(' '), '\u001b[93m \u001b[39m'); 46 | assert.equal(chalk.blue(' '), '\u001b[94m \u001b[39m'); 47 | assert.equal(chalk.magenta(' '), '\u001b[95m \u001b[39m'); 48 | assert.equal(chalk.cyan(' '), '\u001b[96m \u001b[39m'); 49 | assert.equal(chalk.white(' '), '\u001b[97m \u001b[39m'); 50 | }); 51 | 52 | it('should ok w/o colors', function() { 53 | chalk.enabled = false; 54 | chalk.init(); 55 | chalk.setTheme('default'); 56 | 57 | assert.equal(chalk.black(' '), ' '); 58 | assert.equal(chalk.red(' '), ' '); 59 | assert.equal(chalk.green(' '), ' '); 60 | assert.equal(chalk.yellow(' '), ' '); 61 | assert.equal(chalk.blue(' '), ' '); 62 | assert.equal(chalk.magenta(' '), ' '); 63 | assert.equal(chalk.cyan(' '), ' '); 64 | assert.equal(chalk.white(' '), ' '); 65 | }); 66 | 67 | it('should sprint w/ 256 colors ok', function() { 68 | chalk.init(); 69 | chalk.setTheme('default'); 70 | assert.equal(chalk.sprint(' ', '#00ff00'), '\u001b[38;5;46m \u001b[39m'); 71 | }); 72 | 73 | it('should sprint w/ 8 colors ok', function() { 74 | chalk.use256 = false; 75 | chalk.init(); 76 | chalk.setTheme('default'); 77 | assert.equal(chalk.sprint(' ', '#00ff00'), '\u001b[92m \u001b[39m'); 78 | }); 79 | 80 | it('should set theme ok', function() { 81 | chalk.init(); 82 | chalk.setTheme('dark'); 83 | assert.equal(chalk.sprint(' ', '#009900'), chalk.green(' ')); 84 | }); 85 | 86 | it('should set unknown theme ok', function() { 87 | chalk.init(); 88 | chalk.setTheme('unknown'); 89 | assert.equal(chalk.sprint(' ', '#00ff00'), chalk.green(' ')); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/test_config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | const _ = require('underscore'); 5 | 6 | const th = require('./helper'); 7 | 8 | describe('config', function() { 9 | let config; 10 | const FILE = './tmp/config.json'; 11 | 12 | beforeEach(function() { 13 | th.clean(); 14 | 15 | const file = rewire('../lib/file'); 16 | file.configFile = () => FILE; 17 | 18 | config = rewire('../lib/config'); 19 | config.__set__('file', file); 20 | }); 21 | 22 | function createConfigFile(data) { 23 | const fs = require('fs'); 24 | fs.writeFileSync(FILE, JSON.stringify(data)); 25 | } 26 | 27 | it('should ok w/o local config', function() { 28 | const DEFAULT_CONFIG = config.__get__('DEFAULT_CONFIG'); 29 | config.init(); 30 | 31 | let actual = config.getAll(); 32 | let expect = DEFAULT_CONFIG; 33 | assert.deepEqual(actual, expect); 34 | 35 | actual = config.getAll(true); 36 | expect = _.omit(expect, 'sys'); 37 | assert.deepEqual(actual, expect); 38 | }); 39 | 40 | it('should ok w/ local config', function() { 41 | createConfigFile({ 42 | autologin: {enable: false}, 43 | code: {lang: 'ruby'}, 44 | color: {enable: false} 45 | }); 46 | config.init(); 47 | 48 | assert.equal(config.autologin.enable, false); 49 | assert.equal(config.code.lang, 'ruby'); 50 | assert.equal(config.color.enable, false); 51 | assert.equal(config.code.editor, 'vim'); 52 | }); 53 | 54 | it('should remove legacy keys', function() { 55 | createConfigFile({ 56 | USE_COLOR: true, 57 | code: {lang: 'ruby'} 58 | }); 59 | config.init(); 60 | 61 | assert.equal(config.USE_COLOR, undefined); 62 | assert.equal(config.code.lang, 'ruby'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/test_core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | describe('core', function() { 6 | let core; 7 | let next; 8 | 9 | const PROBLEMS = [ 10 | { 11 | category: 'algorithms', 12 | id: 0, 13 | fid: 0, 14 | name: 'name0', 15 | slug: 'slug0', 16 | level: 'Hard', 17 | locked: true, 18 | starred: false, 19 | state: 'ac', 20 | tags: ['google', 'facebook'] 21 | }, 22 | { 23 | category: 'algorithms', 24 | companies: ['amazon', 'facebook'], 25 | id: 1, 26 | fid: 1, 27 | name: 'name1', 28 | slug: 'slug1', 29 | level: 'Easy', 30 | locked: false, 31 | starred: true, 32 | state: 'none' 33 | } 34 | ]; 35 | 36 | before(function() { 37 | const log = require('../lib/log'); 38 | log.init(); 39 | }); 40 | 41 | beforeEach(function() { 42 | next = {}; 43 | next.getProblems = (needTrans, cb) => cb(null, PROBLEMS); 44 | next.getProblem = (p, needTrans, cb) => cb(null, p); 45 | 46 | core = rewire('../lib/core'); 47 | core.setNext(next); 48 | }); 49 | 50 | describe('#filterProblems', function() { 51 | it('should filter by query ok', function(done) { 52 | const cases = [ 53 | ['', [0, 1]], 54 | ['x', [0, 1]], 55 | ['h', [0]], 56 | ['H', [1]], 57 | ['m', []], 58 | ['M', [0, 1]], 59 | ['l', [0]], 60 | ['L', [1]], 61 | ['s', [1]], 62 | ['S', [0]], 63 | ['d', [0]], 64 | ['D', [1]], 65 | ['eLsD', [1]], 66 | ['Dh', []] 67 | ]; 68 | let n = cases.length; 69 | 70 | for (let x of cases) { 71 | core.filterProblems({query: x[0], dontTranslate: false}, function(e, problems) { 72 | assert.notExists(e); 73 | assert.equal(problems.length, x[1].length); 74 | 75 | for (let i = 0; i < problems.length; ++i) 76 | assert.equal(problems[i], PROBLEMS[x[1][i]]); 77 | if (--n === 0) done(); 78 | }); 79 | } 80 | }); 81 | 82 | it('should filter by tag ok', function(done) { 83 | const cases = [ 84 | [[], [0, 1]], 85 | [['facebook'], [0, 1]], 86 | [['google'], [0]], 87 | [['amazon'], [1]], 88 | [['apple'], []], 89 | ]; 90 | let n = cases.length; 91 | 92 | for (let x of cases) { 93 | core.filterProblems({ tag: x[0], dontTranslate: false}, function(e, problems) { 94 | assert.notExists(e); 95 | assert.equal(problems.length, x[1].length); 96 | 97 | for (let i = 0; i < problems.length; ++i) 98 | assert.equal(problems[i], PROBLEMS[x[1][i]]); 99 | if (--n === 0) done(); 100 | }); 101 | } 102 | }); 103 | 104 | it('should fail if getProblems error', function(done) { 105 | next.getProblems = (needT, cb) => cb('getProblems error'); 106 | core.filterProblems({}, function(e) { 107 | assert.equal(e, 'getProblems error'); 108 | done(); 109 | }); 110 | }); 111 | }); // #filterProblems 112 | 113 | describe('#starProblem', function() { 114 | it('should ok', function(done) { 115 | next.starProblem = (p, starred, cb) => cb(null, starred); 116 | 117 | assert.equal(PROBLEMS[0].starred, false); 118 | core.starProblem(PROBLEMS[0], true, function(e, starred) { 119 | assert.notExists(e); 120 | assert.equal(starred, true); 121 | done(); 122 | }); 123 | }); 124 | 125 | it('should ok if already starred', function(done) { 126 | assert.equal(PROBLEMS[1].starred, true); 127 | core.starProblem(PROBLEMS[1], true, function(e, starred) { 128 | assert.notExists(e); 129 | assert.equal(starred, true); 130 | done(); 131 | }); 132 | }); 133 | 134 | it('should ok if already unstarred', function(done) { 135 | assert.equal(PROBLEMS[0].starred, false); 136 | core.starProblem(PROBLEMS[0], false, function(e, starred) { 137 | assert.notExists(e); 138 | assert.equal(starred, false); 139 | done(); 140 | }); 141 | }); 142 | }); // #starProblem 143 | 144 | describe('#exportProblem', function() { 145 | let file; 146 | 147 | beforeEach(function() { 148 | file = rewire('../lib/file'); 149 | file.init(); 150 | core.__set__('file', file); 151 | }); 152 | 153 | it('should codeonly ok', function() { 154 | file.isWindows = () => false; 155 | 156 | const expected = [ 157 | '/*', 158 | ' * @lc app=leetcode id=2 lang=cpp', 159 | ' *', 160 | ' * [2] Add Two Numbers', 161 | ' */', 162 | '', 163 | '// @lc code=start', 164 | '/**', 165 | ' * Definition for singly-linked list.', 166 | ' * struct ListNode {', 167 | ' * int val;', 168 | ' * ListNode *next;', 169 | ' * ListNode(int x) : val(x), next(NULL) {}', 170 | ' * };', 171 | ' */', 172 | 'class Solution {', 173 | 'public:', 174 | ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', 175 | ' ', 176 | ' }', 177 | '};', 178 | '// @lc code=end', 179 | '' 180 | ].join('\n'); 181 | 182 | const problem = require('./mock/add-two-numbers.20161015.json'); 183 | const opts = { 184 | lang: 'cpp', 185 | code: problem.templates[0].defaultCode, 186 | tpl: 'codeonly' 187 | }; 188 | assert.equal(core.exportProblem(problem, opts), expected); 189 | }); 190 | 191 | it('should codeonly ok in windows', function() { 192 | file.isWindows = () => true; 193 | 194 | const expected = [ 195 | '/*', 196 | ' * @lc app=leetcode id=2 lang=cpp', 197 | ' *', 198 | ' * [2] Add Two Numbers', 199 | ' */', 200 | '', 201 | '// @lc code=start', 202 | '/**', 203 | ' * Definition for singly-linked list.', 204 | ' * struct ListNode {', 205 | ' * int val;', 206 | ' * ListNode *next;', 207 | ' * ListNode(int x) : val(x), next(NULL) {}', 208 | ' * };', 209 | ' */', 210 | 'class Solution {', 211 | 'public:', 212 | ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', 213 | ' ', 214 | ' }', 215 | '};', 216 | '// @lc code=end', 217 | '' 218 | ].join('\r\n'); 219 | 220 | const problem = require('./mock/add-two-numbers.20161015.json'); 221 | const opts = { 222 | lang: 'cpp', 223 | code: problem.templates[0].defaultCode, 224 | tpl: 'codeonly' 225 | }; 226 | assert.equal(core.exportProblem(problem, opts), expected); 227 | }); 228 | 229 | it('should detailed ok with cpp', function() { 230 | file.isWindows = () => false; 231 | 232 | const expected = [ 233 | '/*', 234 | ' * @lc app=leetcode id=2 lang=cpp', 235 | ' *', 236 | ' * [2] Add Two Numbers', 237 | ' *', 238 | ' * https://leetcode.com/problems/add-two-numbers', 239 | ' *', 240 | ' * algorithms', 241 | ' * Medium (25.37%)', 242 | ' * Likes: 1', 243 | ' * Dislikes: 1', 244 | ' * Total Accepted: 195263', 245 | ' * Total Submissions: 769711', 246 | ' * Testcase Example: \'[2,4,3]\\n[5,6,4]\'', 247 | ' *', 248 | ' * You are given two linked lists representing two non-negative numbers. The', 249 | ' * digits are stored in reverse order and each of their nodes contain a single', 250 | ' * digit. Add the two numbers and return it as a linked list.', 251 | ' * ', 252 | ' * Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)', 253 | ' * Output: 7 -> 0 -> 8', 254 | ' */', 255 | '', 256 | '// @lc code=start', 257 | '/**', 258 | ' * Definition for singly-linked list.', 259 | ' * struct ListNode {', 260 | ' * int val;', 261 | ' * ListNode *next;', 262 | ' * ListNode(int x) : val(x), next(NULL) {}', 263 | ' * };', 264 | ' */', 265 | 'class Solution {', 266 | 'public:', 267 | ' ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {', 268 | ' ', 269 | ' }', 270 | '};', 271 | '// @lc code=end', 272 | '' 273 | ].join('\n'); 274 | 275 | const problem = require('./mock/add-two-numbers.20161015.json'); 276 | const opts = { 277 | lang: 'cpp', 278 | code: problem.templates[0].defaultCode, 279 | tpl: 'detailed' 280 | }; 281 | assert.equal(core.exportProblem(problem, opts), expected); 282 | }); 283 | 284 | it('should detailed ok with ruby', function() { 285 | file.isWindows = () => false; 286 | 287 | const expected = [ 288 | '#', 289 | '# @lc app=leetcode id=2 lang=ruby', 290 | '#', 291 | '# [2] Add Two Numbers', 292 | '#', 293 | '# https://leetcode.com/problems/add-two-numbers', 294 | '#', 295 | '# algorithms', 296 | '# Medium (25.37%)', 297 | '# Likes: 1', 298 | '# Dislikes: 1', 299 | '# Total Accepted: 195263', 300 | '# Total Submissions: 769711', 301 | '# Testcase Example: \'\'', 302 | '#', 303 | '# You are given two linked lists representing two non-negative numbers. The', 304 | '# digits are stored in reverse order and each of their nodes contain a single', 305 | '# digit. Add the two numbers and return it as a linked list.', 306 | '# ', 307 | '# Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)', 308 | '# Output: 7 -> 0 -> 8', 309 | '#', 310 | '', 311 | '# @lc code=start', 312 | '# Definition for singly-linked list.', 313 | '# class ListNode', 314 | '# attr_accessor :val, :next', 315 | '# def initialize(val)', 316 | '# @val = val', 317 | '# @next = nil', 318 | '# end', 319 | '# end', 320 | '', 321 | '# @param {ListNode} l1', 322 | '# @param {ListNode} l2', 323 | '# @return {ListNode}', 324 | 'def add_two_numbers(l1, l2)', 325 | ' ', 326 | 'end', 327 | '# @lc code=end', 328 | '' 329 | ].join('\n'); 330 | 331 | const problem = require('./mock/add-two-numbers.20161015.json'); 332 | problem.testcase = null; 333 | const opts = { 334 | lang: 'ruby', 335 | code: problem.templates[6].defaultCode, 336 | tpl: 'detailed' 337 | }; 338 | assert.equal(core.exportProblem(problem, opts), expected); 339 | }); 340 | }); // #exportProblem 341 | 342 | describe('#getProblem', function() { 343 | it('should get by id ok', function (done) { 344 | // set needTranslate to false here because it's not used anyways 345 | core.getProblem(0, false, function(e, problem) { 346 | assert.notExists(e); 347 | assert.deepEqual(problem, PROBLEMS[0]); 348 | done(); 349 | }); 350 | }); 351 | 352 | it('should get by key ok', function(done) { 353 | core.getProblem('slug0', false, function(e, problem) { 354 | assert.notExists(e); 355 | assert.deepEqual(problem, PROBLEMS[0]); 356 | done(); 357 | }); 358 | }); 359 | 360 | it('should fail if not found', function(done) { 361 | core.getProblem(3, false, function(e, problem) { 362 | assert.equal(e, 'Problem not found!'); 363 | done(); 364 | }); 365 | }); 366 | 367 | it('should fail if client error', function(done) { 368 | next.getProblem = (problem, needT, cb) => cb('client getProblem error'); 369 | 370 | core.getProblem(0, false, function(e, problem) { 371 | assert.equal(e, 'client getProblem error'); 372 | done(); 373 | }); 374 | }); 375 | 376 | it('should ok if problem is already there', function(done) { 377 | core.getProblem(PROBLEMS[1], false, function(e, problem) { 378 | assert.notExists(e); 379 | assert.deepEqual(problem, PROBLEMS[1]); 380 | done(); 381 | }); 382 | }); 383 | 384 | it('should fail if getProblems error', function(done) { 385 | next.getProblems = (needT, cb) => cb('getProblems error'); 386 | 387 | core.getProblem(0, false, function(e, problem) { 388 | assert.equal(e, 'getProblems error'); 389 | done(); 390 | }); 391 | }); 392 | }); // #getProblem 393 | }); 394 | -------------------------------------------------------------------------------- /test/test_file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const assert = require('chai').assert; 6 | const rewire = require('rewire'); 7 | 8 | const th = require('./helper'); 9 | 10 | describe('file', function() { 11 | let file; 12 | 13 | beforeEach(function() { 14 | file = rewire('../lib/file'); 15 | }); 16 | 17 | describe('#dirAndFiles', function() { 18 | const HOME = path.join(__dirname, '..'); 19 | 20 | it('should ok on linux', function() { 21 | if (file.isWindows()) this.skip(); 22 | process.env.HOME = '/home/skygragon'; 23 | 24 | assert.equal(file.userHomeDir(), '/home/skygragon'); 25 | assert.equal(file.homeDir(), '/home/skygragon/.lc'); 26 | assert.equal(file.cacheDir(), '/home/skygragon/.lc/leetcode/cache'); 27 | assert.equal(file.cacheFile('xxx'), '/home/skygragon/.lc/leetcode/cache/xxx.json'); 28 | assert.equal(file.configFile(), '/home/skygragon/.lc/config.json'); 29 | assert.equal(file.name('/home/skygragon/.lc/leetcode/cache/xxx.json'), 'xxx'); 30 | }); 31 | 32 | it('should ok on windows', function() { 33 | if (!file.isWindows()) this.skip(); 34 | process.env.HOME = ''; 35 | process.env.USERPROFILE = 'C:\\Users\\skygragon'; 36 | assert.equal(file.userHomeDir(), 'C:\\Users\\skygragon'); 37 | assert.equal(file.homeDir(), 'C:\\Users\\skygragon\\.lc'); 38 | assert.equal(file.cacheDir(), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache'); 39 | assert.equal(file.cacheFile('xxx'), 'C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'); 40 | assert.equal(file.configFile(), 'C:\\Users\\skygragon\\.lc\\config.json'); 41 | assert.equal(file.name('C:\\Users\\skygragon\\.lc\\leetcode\\cache\\xxx.json'), 'xxx'); 42 | }); 43 | 44 | it('should codeDir ok', function() { 45 | assert.equal(file.codeDir(), HOME); 46 | assert.equal(file.codeDir('.'), HOME); 47 | assert.equal(file.codeDir('icons'), path.join(HOME, 'icons')); 48 | assert.equal(file.codeDir('lib/plugins'), path.join(HOME, 'lib', 'plugins')); 49 | }); 50 | 51 | it('should listCodeDir ok', function() { 52 | const files = file.listCodeDir('lib/plugins'); 53 | assert.equal(files.length, 6); 54 | assert.equal(files[0].name, 'cache'); 55 | assert.equal(files[1].name, 'company'); 56 | assert.equal(files[2].name, 'leetcode.cn'); 57 | assert.equal(files[3].name, 'leetcode'); 58 | assert.equal(files[4].name, 'retry'); 59 | assert.equal(files[5].name, 'solution.discuss'); 60 | }); 61 | 62 | it('should pluginFile ok', function() { 63 | const expect = path.join(HOME, 'lib/plugins/cache.js'); 64 | assert.equal(file.pluginFile('cache.js'), expect); 65 | assert.equal(file.pluginFile('./cache.js'), expect); 66 | assert.equal(file.pluginFile('https://github.com/skygragon/cache.js'), expect); 67 | }); 68 | 69 | it('should data ok with missing file', function() { 70 | assert.equal(file.data('non-exist'), null); 71 | }); 72 | }); // #dirAndFiles 73 | 74 | describe('#meta', function() { 75 | it('should meta ok within file content', function() { 76 | file.data = x => [ 77 | '/ *', 78 | ' * @lc app=leetcode id=123 lang=javascript', 79 | ' * /' 80 | ].join('\n'); 81 | const meta = file.meta('dummy'); 82 | assert.equal(meta.app, 'leetcode') 83 | assert.equal(meta.id, '123'); 84 | assert.equal(meta.lang, 'javascript'); 85 | }); 86 | 87 | it('should meta ok with white space', function() { 88 | file.data = x => [ 89 | '/ *', 90 | ' * @lc app=leetcode id=123\t \t lang=javascript\r', 91 | ' * /' 92 | ].join('\n'); 93 | const meta = file.meta('dummy'); 94 | assert.equal(meta.app, 'leetcode') 95 | assert.equal(meta.id, '123'); 96 | assert.equal(meta.lang, 'javascript'); 97 | }); 98 | 99 | it('should meta ok within file name', function() { 100 | file.data = x => [ 101 | '/ *', 102 | ' * no meta app=leetcode id=123 lang=javascript', 103 | ' * /' 104 | ].join('\n'); 105 | const meta = file.meta('321.dummy.py'); 106 | assert(!meta.app) 107 | assert.equal(meta.id, '321'); 108 | assert.equal(meta.lang, 'python'); 109 | }); 110 | 111 | it('should meta ok within deprecated file name', function() { 112 | file.data = x => [ 113 | '/ *', 114 | ' * no meta app=leetcode id=123 lang=javascript', 115 | ' * /' 116 | ].join('\n'); 117 | 118 | var meta = file.meta('111.dummy.py3'); 119 | assert(!meta.app) 120 | assert.equal(meta.id, '111'); 121 | assert.equal(meta.lang, 'python3'); 122 | 123 | meta = file.meta('222.dummy.python3.py'); 124 | assert(!meta.app) 125 | assert.equal(meta.id, '222'); 126 | assert.equal(meta.lang, 'python3'); 127 | }); 128 | 129 | it('should fmt ok', function() { 130 | file.init(); 131 | const data = file.fmt('${id}', {id: 123}); 132 | assert.equal(data, '123'); 133 | }); 134 | }); // #meta 135 | 136 | describe('#genneral', function() { 137 | beforeEach(function() { 138 | th.clean(); 139 | }); 140 | afterEach(function() { 141 | th.clean(); 142 | }); 143 | 144 | it('should mkdir ok', function() { 145 | const dir = th.DIR + 'dir'; 146 | assert.equal(fs.existsSync(dir), false); 147 | file.mkdir(dir); 148 | assert.equal(fs.existsSync(dir), true); 149 | file.mkdir(dir); 150 | assert.equal(fs.existsSync(dir), true); 151 | }); 152 | 153 | it('should mv ok', function() { 154 | const SRC = th.Dir + 'src'; 155 | const DST = th.DIR + 'dst'; 156 | assert.equal(fs.existsSync(SRC), false); 157 | assert.equal(fs.existsSync(DST), false); 158 | file.mkdir(SRC); 159 | assert.equal(fs.existsSync(SRC), true); 160 | assert.equal(fs.existsSync(DST), false); 161 | file.mv(SRC, DST); 162 | assert.equal(fs.existsSync(SRC), false); 163 | assert.equal(fs.existsSync(DST), true); 164 | }); 165 | }); // #general 166 | }); 167 | -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | const _ = require('underscore'); 5 | 6 | const chalk = require('../lib/chalk'); 7 | 8 | describe('helper', function() { 9 | let h; 10 | 11 | before(function() { 12 | chalk.init(); 13 | }); 14 | 15 | beforeEach(function() { 16 | h = rewire('../lib/helper'); 17 | }); 18 | 19 | describe('#prettyState', function() { 20 | it('should ok w/ color', function() { 21 | chalk.enabled = true; 22 | 23 | assert.equal(h.prettyState('ac'), chalk.green('✔')); 24 | assert.equal(h.prettyState('notac'), chalk.red('✘')); 25 | assert.equal(h.prettyState('none'), ' '); 26 | assert.equal(h.prettyState(''), ' '); 27 | assert.equal(h.prettyState(null), ' '); 28 | }); 29 | 30 | it('should ok w/o color', function() { 31 | chalk.enabled = false; 32 | 33 | assert.equal(h.prettyState('ac'), '✔'); 34 | assert.equal(h.prettyState('notac'), '✘'); 35 | assert.equal(h.prettyState('none'), ' '); 36 | assert.equal(h.prettyState(''), ' '); 37 | assert.equal(h.prettyState(null), ' '); 38 | }); 39 | }); // #prettyState 40 | 41 | describe('#prettyText', function() { 42 | it('should ok w/ color', function() { 43 | chalk.enabled = true; 44 | 45 | assert.equal(h.prettyText(' text', true), chalk.green('✔ text')); 46 | assert.equal(h.prettyText(' text', false), chalk.red('✘ text')); 47 | assert.equal(h.prettyText('text'), 'text'); 48 | }); 49 | 50 | it('should ok w/o color', function() { 51 | chalk.enabled = false; 52 | 53 | assert.equal(h.prettyText(' text', true), '✔ text'); 54 | assert.equal(h.prettyText(' text', false), '✘ text'); 55 | assert.equal(h.prettyText('text'), 'text'); 56 | }); 57 | }); // #prettyText 58 | 59 | describe('#prettyLevel', function() { 60 | it('should ok w/ color', function() { 61 | chalk.enabled = true; 62 | 63 | assert.equal(h.prettyLevel('Easy'), chalk.green('Easy')); 64 | assert.equal(h.prettyLevel('Medium'), chalk.yellow('Medium')); 65 | assert.equal(h.prettyLevel('Hard'), chalk.red('Hard')); 66 | assert.equal(h.prettyLevel('easy '), chalk.green('easy ')); 67 | assert.equal(h.prettyLevel('medium'), chalk.yellow('medium')); 68 | assert.equal(h.prettyLevel('hard '), chalk.red('hard ')); 69 | assert.equal(h.prettyLevel('unknown'), 'unknown'); 70 | }); 71 | }); // #prettyLevel 72 | 73 | describe('#prettySize', function() { 74 | it('should ok', function() { 75 | assert.equal(h.prettySize(0), '0.00B'); 76 | assert.equal(h.prettySize(512), '512.00B'); 77 | assert.equal(h.prettySize(1024), '1.00K'); 78 | assert.equal(h.prettySize(1024 * 1024), '1.00M'); 79 | assert.equal(h.prettySize(1024 * 1024 * 1024), '1.00G'); 80 | }); 81 | }); // #prettySize 82 | 83 | describe('#prettyTime', function() { 84 | it('should ok', function() { 85 | assert.equal(h.prettyTime(30), '30 seconds'); 86 | assert.equal(h.prettyTime(60), '1 minutes'); 87 | assert.equal(h.prettyTime(2400), '40 minutes'); 88 | assert.equal(h.prettyTime(3600), '1 hours'); 89 | assert.equal(h.prettyTime(7200), '2 hours'); 90 | assert.equal(h.prettyTime(86400), '1 days'); 91 | assert.equal(h.prettyTime(86400 * 3), '3 days'); 92 | assert.equal(h.prettyTime(86400 * 7), '1 weeks'); 93 | }); 94 | }); // #prettyTime 95 | 96 | describe('#levelToName', function() { 97 | it('should ok', function() { 98 | assert.equal(h.levelToName(0), ' '); 99 | assert.equal(h.levelToName(1), 'Easy'); 100 | assert.equal(h.levelToName(2), 'Medium'); 101 | assert.equal(h.levelToName(3), 'Hard'); 102 | assert.equal(h.levelToName(4), ' '); 103 | }); 104 | }); // #levelToName 105 | 106 | describe('#statusToName', function() { 107 | it('should ok', function() { 108 | assert.equal(h.statusToName(10), 'Accepted'); 109 | assert.equal(h.statusToName(11), 'Wrong Answer'); 110 | assert.equal(h.statusToName(12), 'Memory Limit Exceeded'); 111 | assert.equal(h.statusToName(13), 'Output Limit Exceeded'); 112 | assert.equal(h.statusToName(14), 'Time Limit Exceeded'); 113 | assert.equal(h.statusToName(15), 'Runtime Error'); 114 | assert.equal(h.statusToName(16), 'Internal Error'); 115 | assert.equal(h.statusToName(20), 'Compile Error'); 116 | assert.equal(h.statusToName(21), 'Unknown Error'); 117 | assert.equal(h.statusToName(99), 'Unknown'); 118 | }); 119 | }); // #statusToName 120 | 121 | describe('#langToExt', function() { 122 | it('should ok', function() { 123 | assert.equal(h.langToExt('bash'), '.sh'); 124 | assert.equal(h.langToExt('c'), '.c'); 125 | assert.equal(h.langToExt('cpp'), '.cpp'); 126 | assert.equal(h.langToExt('csharp'), '.cs'); 127 | assert.equal(h.langToExt('golang'), '.go'); 128 | assert.equal(h.langToExt('java'), '.java'); 129 | assert.equal(h.langToExt('javascript'), '.js'); 130 | assert.equal(h.langToExt('mysql'), '.sql'); 131 | assert.equal(h.langToExt('php'), '.php'); 132 | assert.equal(h.langToExt('python'), '.py'); 133 | assert.equal(h.langToExt('python3'), '.py'); 134 | assert.equal(h.langToExt('ruby'), '.rb'); 135 | assert.equal(h.langToExt('rust'), '.rs'); 136 | assert.equal(h.langToExt('scala'), '.scala'); 137 | assert.equal(h.langToExt('swift'), '.swift'); 138 | assert.equal(h.langToExt('rust'), '.rs'); 139 | assert.equal(h.langToExt('typescript'), '.ts'); 140 | }); 141 | }); // #langToExt 142 | 143 | describe('#extToLang', function() { 144 | it('should ok', function() { 145 | assert.equal(h.extToLang('/usr/bin/file.sh'), 'bash'); 146 | assert.equal(h.extToLang('/home/skygragon/file.c'), 'c'); 147 | assert.equal(h.extToLang('/var/log/file.cpp'), 'cpp'); 148 | assert.equal(h.extToLang('./file.cs'), 'csharp'); 149 | assert.equal(h.extToLang('../file.go'), 'golang'); 150 | assert.equal(h.extToLang('file.java'), 'java'); 151 | assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); 152 | assert.equal(h.extToLang('~/leetcode/hello.php'), 'php'); 153 | assert.equal(h.extToLang('c:/file.js'), 'javascript'); 154 | assert.equal(h.extToLang('c:/Users/skygragon/file.py'), 'python'); 155 | assert.equal(h.extToLang('~/file.rb'), 'ruby'); 156 | assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); 157 | assert.equal(h.extToLang('/tmp/file.scala'), 'scala'); 158 | assert.equal(h.extToLang('~/leetcode/file.swift'), 'swift'); 159 | assert.equal(h.extToLang('~/leetcode/../file.sql'), 'mysql'); 160 | assert.equal(h.extToLang('/home/skygragon/file.dat'), 'unknown'); 161 | assert.equal(h.extToLang('~/leetcode/file.rs'), 'rust'); 162 | assert.equal(h.extToLang('~/leetcode/file.ts'), 'typescript'); 163 | }); 164 | }); // #extToLang 165 | 166 | describe('#langToCommentStyle', function() { 167 | it('should ok', function() { 168 | const C_STYLE = {start: '/*', line: ' *', end: ' */', singleLine: '//'}; 169 | const RUBY_STYLE = {start: '#', line: '#', end: '#', singleLine: '#'}; 170 | const SQL_STYLE = {start: '--', line: '--', end: '--', singleLine: '--'}; 171 | 172 | assert.deepEqual(h.langToCommentStyle('bash'), RUBY_STYLE); 173 | assert.deepEqual(h.langToCommentStyle('c'), C_STYLE); 174 | assert.deepEqual(h.langToCommentStyle('cpp'), C_STYLE); 175 | assert.deepEqual(h.langToCommentStyle('csharp'), C_STYLE); 176 | assert.deepEqual(h.langToCommentStyle('golang'), C_STYLE); 177 | assert.deepEqual(h.langToCommentStyle('php'), C_STYLE); 178 | assert.deepEqual(h.langToCommentStyle('java'), C_STYLE); 179 | assert.deepEqual(h.langToCommentStyle('javascript'), C_STYLE); 180 | assert.deepEqual(h.langToCommentStyle('mysql'), SQL_STYLE); 181 | assert.deepEqual(h.langToCommentStyle('rust'), C_STYLE); 182 | assert.deepEqual(h.langToCommentStyle('python'), RUBY_STYLE); 183 | assert.deepEqual(h.langToCommentStyle('python3'), RUBY_STYLE); 184 | assert.deepEqual(h.langToCommentStyle('ruby'), RUBY_STYLE); 185 | assert.deepEqual(h.langToCommentStyle('scala'), C_STYLE); 186 | assert.deepEqual(h.langToCommentStyle('swift'), C_STYLE); 187 | assert.deepEqual(h.langToCommentStyle('typescript'), C_STYLE); 188 | }); 189 | }); // #langToCommentStyle 190 | 191 | describe('#getSetCookieValue', function() { 192 | it('should ok', function() { 193 | const resp = { 194 | headers: {'set-cookie': [ 195 | 'key1=value1; path=/; Httponly', 196 | 'key2=value2; path=/; Httponly'] 197 | } 198 | }; 199 | const respNoSetCookie = { 200 | headers: {} 201 | }; 202 | 203 | assert.equal(h.getSetCookieValue(resp, 'key1'), 'value1'); 204 | assert.equal(h.getSetCookieValue(resp, 'key2'), 'value2'); 205 | assert.equal(h.getSetCookieValue(resp, 'key3'), null); 206 | assert.equal(h.getSetCookieValue(respNoSetCookie, 'key1'), null); 207 | }); 208 | }); // #getSetCookieValue 209 | 210 | describe('#printSafeHTTP', function() { 211 | it('should hide sensitive info', function() { 212 | const raw = [ 213 | "Cookie: 'xxxxxx'", 214 | "'X-CSRFToken': 'yyyyyy'", 215 | "'set-cookie': ['zzzzzz']" 216 | ].join('\r\n'); 217 | 218 | const hide = [ 219 | 'Cookie:', 220 | "'X-CSRFToken': ", 221 | "'set-cookie': " 222 | ].join('\r\n'); 223 | 224 | assert.equal(h.printSafeHTTP(raw), hide); 225 | }); 226 | }); // #printSafeHTTP 227 | 228 | describe('#readStdin', function() { 229 | function hijackStdin(data) { 230 | const stream = require('stream'); 231 | const rs = new stream.Readable(); 232 | rs.push(data); 233 | rs.push(null); 234 | 235 | Object.defineProperty(process, 'stdin', {value: rs}); 236 | } 237 | 238 | it('should ok', function(done) { 239 | hijackStdin('[1,2]\n3'); 240 | 241 | h.readStdin(function(e, data) { 242 | assert.equal(data, '[1,2]\n3'); 243 | done(); 244 | }); 245 | }); 246 | 247 | it('should ok w/ empty input', function(done) { 248 | hijackStdin(''); 249 | 250 | h.readStdin(function(e, data) { 251 | assert.equal(data, ''); 252 | done(); 253 | }); 254 | }); 255 | }); // #readStdin 256 | 257 | describe('#badge', function() { 258 | it('should ok', function() { 259 | chalk.enabled = true; 260 | assert.equal(h.badge('x'), chalk.white.bgBlue(' x ')); 261 | assert.equal(h.badge('x', 'green'), chalk.black.bgGreen(' x ')); 262 | }); 263 | 264 | it('should ok with random', function() { 265 | const badges = _.values(h.__get__('COLORS')) 266 | .map(function(x) { 267 | return chalk[x.fg][x.bg](' random '); 268 | }); 269 | 270 | const i = badges.indexOf(h.badge('random', 'random')); 271 | assert.equal(i >= 0, true); 272 | }); 273 | }); // #badge 274 | }); 275 | -------------------------------------------------------------------------------- /test/test_icon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | describe('icon', function() { 6 | let icon; 7 | let file; 8 | 9 | beforeEach(function() { 10 | file = rewire('../lib/file'); 11 | file.listCodeDir = function() { 12 | return [ 13 | {name: 'mac', data: {yes: 'yes', no: 'no', lock: 'lock', like: 'like', unlike: 'unlike'}}, 14 | {name: 'win7', data: {yes: 'YES', no: 'NO', lock: 'LOCK', like: 'LIKE', unlike: 'UNLIKE'}} 15 | ]; 16 | }; 17 | 18 | icon = rewire('../lib/icon'); 19 | icon.__set__('file', file); 20 | icon.init(); 21 | }); 22 | 23 | describe('#setTheme', function() { 24 | it('should ok with known theme', function() { 25 | icon.setTheme('mac'); 26 | assert.equal(icon.yes, 'yes'); 27 | assert.equal(icon.no, 'no'); 28 | assert.equal(icon.lock, 'lock'); 29 | assert.equal(icon.like, 'like'); 30 | assert.equal(icon.unlike, 'unlike'); 31 | }); 32 | 33 | it('should ok with unknown theme on linux', function() { 34 | file.isWindows = () => false; 35 | 36 | icon.setTheme('non-exist'); 37 | assert.equal(icon.yes, '✔'); 38 | assert.equal(icon.no, '✘'); 39 | assert.equal(icon.lock, '🔒'); 40 | assert.equal(icon.like, '★'); 41 | assert.equal(icon.unlike, '☆'); 42 | }); 43 | 44 | it('should ok with unknown theme on windows', function() { 45 | file.isWindows = () => true; 46 | 47 | icon.setTheme('non-exist'); 48 | assert.equal(icon.yes, 'YES'); 49 | assert.equal(icon.no, 'NO'); 50 | assert.equal(icon.lock, 'LOCK'); 51 | assert.equal(icon.like, 'LIKE'); 52 | assert.equal(icon.unlike, 'UNLIKE'); 53 | }); 54 | }); // #setTheme 55 | }); 56 | -------------------------------------------------------------------------------- /test/test_log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | const chalk = require('../lib/chalk'); 6 | 7 | describe('log', function() { 8 | let log; 9 | let savedOutput; 10 | let expected; 11 | 12 | before(function() { 13 | chalk.init(); 14 | }); 15 | 16 | beforeEach(function() { 17 | log = rewire('../lib/log'); 18 | savedOutput = log.output; 19 | log.output = x => expected = x; 20 | 21 | log.init(); 22 | expected = ''; 23 | }); 24 | 25 | afterEach(function() { 26 | log.output = savedOutput; 27 | }); 28 | 29 | describe('#setLevel', function() { 30 | it('should ok with known level', function() { 31 | log.setLevel('TRACE'); 32 | assert.deepEqual(log.level, log.levels.get('TRACE')); 33 | log.setLevel('DEBUG'); 34 | assert.deepEqual(log.level, log.levels.get('DEBUG')); 35 | log.setLevel('INFO'); 36 | assert.deepEqual(log.level, log.levels.get('INFO')); 37 | log.setLevel('WARN'); 38 | assert.deepEqual(log.level, log.levels.get('WARN')); 39 | log.setLevel('ERROR'); 40 | assert.deepEqual(log.level, log.levels.get('ERROR')); 41 | }); 42 | 43 | it('should ok with unknown level', function() { 44 | log.setLevel(''); 45 | assert.deepEqual(log.level, log.levels.get('INFO')); 46 | }); 47 | }); // #setLevel 48 | 49 | describe('#isEnabled', function() { 50 | it('should ok', function() { 51 | log.setLevel('DEBUG'); 52 | assert.equal(log.isEnabled('TRACE'), false); 53 | assert.equal(log.isEnabled('DEBUG'), true); 54 | assert.equal(log.isEnabled('INFO'), true); 55 | assert.equal(log.isEnabled('WARN'), true); 56 | assert.equal(log.isEnabled('ERROR'), true); 57 | }); 58 | }); // #isEnabled 59 | 60 | describe('#levels', function() { 61 | it('should ok with log.trace', function() { 62 | log.trace('some error'); 63 | assert.equal(expected, ''); 64 | 65 | log.setLevel('TRACE'); 66 | log.trace('some error'); 67 | assert.equal(expected, chalk.gray('[TRACE] some error')); 68 | }); 69 | 70 | it('should ok with log.debug', function() { 71 | log.debug('some error'); 72 | assert.equal(expected, ''); 73 | 74 | log.setLevel('DEBUG'); 75 | log.debug('some error'); 76 | assert.equal(expected, chalk.gray('[DEBUG] some error')); 77 | }); 78 | 79 | it('should ok with log.info', function() { 80 | log.info('some error'); 81 | assert.equal(expected, 'some error'); 82 | }); 83 | 84 | it('should ok with log.warn', function() { 85 | log.warn('some error'); 86 | assert.equal(expected, chalk.yellow('[WARN] some error')); 87 | }); 88 | 89 | it('should ok with log.error', function() { 90 | log.error('some error'); 91 | assert.equal(expected, chalk.red('[ERROR] some error')); 92 | }); 93 | 94 | it('should ok with log.fail', function() { 95 | log.fail({msg: 'some error', statusCode: 500}); 96 | assert.equal(expected, chalk.red('[ERROR] some error [code=500]')); 97 | 98 | log.fail('some error'); 99 | assert.equal(expected, chalk.red('[ERROR] some error')); 100 | }); 101 | }); // #levels 102 | 103 | describe('#printf', function() { 104 | it('should ok', function() { 105 | log.printf('%s and %s and %%', 'string', 100); 106 | assert.equal(expected, 'string and 100 and %'); 107 | }); 108 | }); // #printf 109 | }); 110 | -------------------------------------------------------------------------------- /test/test_plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const assert = require('chai').assert; 6 | const rewire = require('rewire'); 7 | 8 | const chalk = require('../lib/chalk'); 9 | const config = require('../lib/config'); 10 | const log = require('../lib/log'); 11 | const th = require('./helper'); 12 | 13 | const Plugin = rewire('../lib/plugin'); 14 | 15 | describe('plugin', function() { 16 | let file; 17 | let cache; 18 | 19 | const NOOP = () => {}; 20 | 21 | before(function() { 22 | log.init(); 23 | chalk.init(); 24 | config.init(); 25 | 26 | file = rewire('../lib/file'); 27 | cache = rewire('../lib/cache'); 28 | Plugin.__set__('file', file); 29 | Plugin.__set__('cache', cache); 30 | }); 31 | 32 | beforeEach(function() { 33 | th.clean(); 34 | cache.get = NOOP; 35 | }); 36 | 37 | describe('#Plugin.init', function() { 38 | const p1 = new Plugin(0, 'leetcode', '2.0'); 39 | const p2 = new Plugin(1, 'cache', '1.0'); 40 | const p3 = new Plugin(2, 'retry', '3.0'); 41 | const p4 = new Plugin(3, 'core', '4.0'); 42 | 43 | before(function() { 44 | p1.init = p2.init = p3.init = p4.init = NOOP; 45 | file.listCodeDir = function() { 46 | return [ 47 | {name: 'cache', data: p2, file: 'cache.js'}, 48 | {name: 'leetcode', data: p1, file: 'leetcode.js'}, 49 | {name: 'retry', data: p3, file: 'retry.js'}, 50 | {name: 'bad', data: null} 51 | ]; 52 | }; 53 | }); 54 | 55 | it('should init ok', function() { 56 | cache.get = () => { 57 | return {cache: true, leetcode: false, retry: true}; 58 | }; 59 | assert.deepEqual(Plugin.plugins, []); 60 | 61 | const res = Plugin.init(p4); 62 | assert.equal(res, true); 63 | assert.deepEqual(Plugin.plugins.length, 3); 64 | 65 | const names = Plugin.plugins.map(p => p.name); 66 | assert.deepEqual(names, ['retry', 'cache', 'leetcode']); 67 | 68 | assert.equal(p4.next, p3); 69 | assert.equal(p3.next, p2); 70 | assert.equal(p2.next, null); 71 | assert.equal(p1.next, null); 72 | }); 73 | 74 | it('should find missing ok', function() { 75 | cache.get = () => { 76 | return {company: true, leetcode: false, solution: true}; 77 | }; 78 | 79 | const res = Plugin.init(p4); 80 | assert.equal(res, false); 81 | assert.deepEqual(Plugin.plugins.length, 5); 82 | 83 | const names = Plugin.plugins.map(p => p.name); 84 | assert.deepEqual(names, ['retry', 'cache', 'leetcode', 'company', 'solution']); 85 | 86 | assert.equal(p4.next, p3); 87 | assert.equal(p3.next, p2); 88 | assert.equal(p2.next, null); 89 | assert.equal(p1.next, null); 90 | }); 91 | }); // #Plugin.init 92 | 93 | describe('#install', function() { 94 | let expected; 95 | 96 | before(function() { 97 | Plugin.__set__('cp', { 98 | exec: function(cmd, opts, cb) { 99 | expected = cmd; 100 | return cb(); 101 | } 102 | }); 103 | }); 104 | 105 | it('should install no deps ok', function(done) { 106 | expected = ''; 107 | const p = new Plugin(100, 'test', '2017.12.26', 'desc', []); 108 | p.install(function() { 109 | assert.equal(expected, ''); 110 | done(); 111 | }); 112 | }); 113 | 114 | it('should install deps ok', function(done) { 115 | const deps = ['a', 'b:linux', 'b:darwin', 'b:win32', 'c:bad', 'd']; 116 | const p = new Plugin(100, 'test', '2017.12.26', 'desc', deps); 117 | p.install(function() { 118 | assert.equal(expected, 'npm install --save a b d'); 119 | done(); 120 | }); 121 | }); 122 | }); // #install 123 | 124 | describe('#Plugin.copy', function() { 125 | const SRC = path.resolve(th.DIR, 'copy.src.js'); 126 | const DST = path.resolve(th.DIR, 'copy.test.js'); 127 | 128 | before(function() { 129 | file.pluginFile = () => DST; 130 | }); 131 | 132 | it('should copy from http error', function(done) { 133 | Plugin.copy('non-exists', function(e, fullpath) { 134 | assert.equal(e, 'HTTP Error: 404'); 135 | assert.equal(fs.existsSync(DST), false); 136 | done(); 137 | }); 138 | }).timeout(5000); 139 | 140 | it('should copy from local ok', function(done) { 141 | const data = [ 142 | 'module.exports = {', 143 | ' x: 123,', 144 | ' install: function(cb) { cb(); }', 145 | '};' 146 | ]; 147 | fs.writeFileSync(SRC, data.join('\n')); 148 | 149 | Plugin.copy(SRC, function(e, fullpath) { 150 | assert.notExists(e); 151 | assert.equal(fullpath, DST); 152 | assert.equal(fs.existsSync(DST), true); 153 | done(); 154 | }); 155 | }); 156 | }); // #Plugin.copy 157 | 158 | describe('#Plugin.installMissings', function() { 159 | const PLUGINS = [ 160 | new Plugin(0, '0', 'missing'), 161 | new Plugin(1, '1', '2018.01.01'), 162 | new Plugin(2, '2', 'missing'), 163 | ]; 164 | let expected; 165 | 166 | beforeEach(function() { 167 | expected = []; 168 | file.pluginFile = x => th.DIR + x; 169 | Plugin.install = (name, cb) => { 170 | expected.push(name); 171 | return cb(null, PLUGINS[+name]); 172 | }; 173 | }); 174 | 175 | it('should ok', function(done) { 176 | Plugin.plugins = PLUGINS; 177 | Plugin.installMissings(function(e) { 178 | assert.notExists(e); 179 | assert.deepEqual(expected, ['0', '2']); 180 | done(); 181 | }); 182 | }); 183 | }); // #Plugin.installMissings 184 | 185 | describe('#delete', function() { 186 | it('should ok', function() { 187 | file.pluginFile = x => th.DIR + x; 188 | 189 | const p = new Plugin(0, '0', '2018.01.01'); 190 | p.file = '0.js'; 191 | fs.writeFileSync('./tmp/0.js', ''); 192 | 193 | assert.equal(p.deleted, false); 194 | assert.deepEqual(fs.readdirSync(th.DIR), ['0.js']); 195 | p.delete(); 196 | assert.equal(p.deleted, true); 197 | assert.deepEqual(fs.readdirSync(th.DIR), []); 198 | p.delete(); 199 | assert.equal(p.deleted, true); 200 | assert.deepEqual(fs.readdirSync(th.DIR), []); 201 | }); 202 | }); // #delete 203 | 204 | describe('#save', function() { 205 | it('should ok', function() { 206 | let data = {}; 207 | cache.get = () => data; 208 | cache.set = (k, x) => data = x; 209 | 210 | const p = new Plugin(0, '0', '2018.01.01'); 211 | p.save(); 212 | assert.deepEqual(data, {'0': true}); 213 | 214 | p.enabled = false; 215 | p.save(); 216 | assert.deepEqual(data, {'0': false}); 217 | 218 | p.deleted = true; 219 | p.save(); 220 | assert.deepEqual(data, {}); 221 | }); 222 | }); // #save 223 | }); 224 | -------------------------------------------------------------------------------- /test/test_queue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | 6 | 7 | describe('queue', function() { 8 | let Queue; 9 | 10 | beforeEach(function() { 11 | Queue = rewire('../lib/queue'); 12 | }); 13 | 14 | it('should ok', function(done) { 15 | function doTask(x, q, cb) { 16 | ++q.ctx.n; 17 | q.ctx.sum += x; 18 | return cb(); 19 | } 20 | 21 | const ctx = {n: 0, sum: 0}; 22 | const q = new Queue([], ctx, doTask); 23 | 24 | q.addTask(1); 25 | q.addTask(2); 26 | q.addTasks([3, 4, 5]); 27 | 28 | q.run(5, function(e, ctx) { 29 | assert.notExists(e); 30 | assert.equal(ctx.n, 5); 31 | assert.equal(ctx.sum, 15); 32 | done(); 33 | }); 34 | }); 35 | 36 | it('should ok in sequence', function(done) { 37 | const config = {network: {}}; 38 | Queue.__set__('config', config); 39 | 40 | function doTask(x, q, cb) { 41 | if (!q.ctx.list) q.ctx.list = []; 42 | q.ctx.list.push(x); 43 | return cb(); 44 | } 45 | 46 | const q = new Queue(null, null, doTask); 47 | q.addTask(1); 48 | q.addTasks([2, 3]); 49 | q.addTasks([4]); 50 | q.addTask(5); 51 | 52 | q.run(null, function(e, ctx) { 53 | assert.notExists(e); 54 | assert.deepEqual(ctx.list, [1, 2, 3, 4, 5]); 55 | done(); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/test_session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | describe('session', function() { 6 | let session; 7 | let stats; 8 | let now; 9 | 10 | beforeEach(function() { 11 | stats = null; 12 | const cache = { 13 | get: (k) => stats, 14 | set: (k, v) => stats = v 15 | }; 16 | const moment = () => { 17 | return {format: () => now} 18 | }; 19 | 20 | session = rewire('../lib/session'); 21 | session.__set__('cache', cache); 22 | session.__set__('moment', moment); 23 | }); 24 | 25 | describe('#updateStat', function() { 26 | it('should update number ok', function() { 27 | now = '2017.12.13'; 28 | session.updateStat('ac', 10); 29 | assert.deepEqual(stats, {'2017.12.13': {ac: 10}}); 30 | 31 | session.updateStat('ac', 20); 32 | assert.deepEqual(stats, {'2017.12.13': {ac: 30}}); 33 | 34 | now = '2017.12.14'; 35 | session.updateStat('ac', 40); 36 | assert.deepEqual(stats, { 37 | '2017.12.13': {ac: 30}, 38 | '2017.12.14': {ac: 40} 39 | }); 40 | }); 41 | 42 | it('should update set ok', function() { 43 | now = '2017.12.13'; 44 | session.updateStat('ac.set', 101); 45 | assert.deepEqual(stats, {'2017.12.13': {'ac.set': [101]}}); 46 | session.updateStat('ac.set', 100); 47 | assert.deepEqual(stats, {'2017.12.13': {'ac.set': [101, 100]}}); 48 | session.updateStat('ac.set', 101); 49 | assert.deepEqual(stats, {'2017.12.13': {'ac.set': [101, 100]}}); 50 | }); 51 | }); // #updateStat 52 | }); 53 | -------------------------------------------------------------------------------- /test/test_sprintf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('chai').assert; 3 | const rewire = require('rewire'); 4 | 5 | const sprintf = require('../lib/sprintf'); 6 | 7 | describe('sprintf', function() { 8 | it('should ok', function() { 9 | assert.equal(sprintf('%%'), '%'); 10 | assert.equal(sprintf('%s', 123), '123'); 11 | assert.equal(sprintf('%6s', 123), ' 123'); 12 | assert.equal(sprintf('%06s', 123), '000123'); 13 | assert.equal(sprintf('%-6s', 123), '123 '); 14 | assert.equal(sprintf('%=6s', 123), ' 123 '); 15 | 16 | assert.equal(sprintf('%4s,%=4s,%-4s', 123, 'xy', 3.1), ' 123, xy ,3.1 '); 17 | }); 18 | 19 | it('should non-ascii ok', function() { 20 | assert.equal(sprintf('%4s', '中'), ' 中'); 21 | assert.equal(sprintf('%-4s', '中'), '中 '); 22 | assert.equal(sprintf('%=4s', '中'), ' 中 '); 23 | 24 | assert.equal(sprintf('%=14s', '12你好34世界'), ' 12你好34世界 '); 25 | }); 26 | 27 | it('should color ok', function() { 28 | const chalk = rewire('../lib/chalk'); 29 | chalk.init(); 30 | 31 | assert.equal(sprintf('%=3s', chalk.red('X')), ' ' + chalk.red('X') + ' '); 32 | }); 33 | }); 34 | --------------------------------------------------------------------------------