Repository: mwouts/jupytext
Pull request #250 (2019-06-08 18:45:13)

add support for javascript and typescript
this PR adds support for files using [IJavascript](https://github.com/n-riesco/ijavascript) and [ITypescript](https://github.com/nearbydelta/itypescript) kernels.

as discussed in #249

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/mwouts/jupytext/250)
<!-- Reviewable:end -->


mwouts: Excellent! Thanks @hatemhosny . I will review this shortly.
codecov[bot]: # [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=h1) Report
> Merging [#250](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=desc) into [master](https://codecov.io/gh/mwouts/jupytext/commit/2d97aaff7596242041b9106f7c6a889cc22db120?src=pr&el=desc) will **decrease** coverage by `1.3%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/mwouts/jupytext/pull/250/graphs/tree.svg?width=650&token=a1pNjsiILi&height=150&src=pr)](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #250      +/-   ##
==========================================
- Coverage   99.07%   97.77%   -1.31%     
==========================================
  Files          66       66              
  Lines        6286     6294       +8     
==========================================
- Hits         6228     6154      -74     
- Misses         58      140      +82
```


| [Impacted Files](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [jupytext/kernels.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-anVweXRleHQva2VybmVscy5weQ==) | `87.5% <ø> (-4.17%)` | :arrow_down: |
| [tests/test\_pep8.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9wZXA4LnB5) | `98.36% <ø> (ø)` | :arrow_up: |
| [jupytext/languages.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-anVweXRleHQvbGFuZ3VhZ2VzLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [tests/test\_mirror.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9taXJyb3IucHk=) | `98.66% <100%> (-1.34%)` | :arrow_down: |
| [tests/test\_read\_simple\_pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9wYW5kb2MucHk=) | `44.82% <0%> (-55.18%)` | :arrow_down: |
| [jupytext/pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-anVweXRleHQvcGFuZG9jLnB5) | `43.75% <0%> (-52.09%)` | :arrow_down: |
| [jupytext/jupytext.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-anVweXRleHQvanVweXRleHQucHk=) | `95.28% <0%> (-4.72%)` | :arrow_down: |
| [tests/test\_cli.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jbGkucHk=) | `97.56% <0%> (-2.44%)` | :arrow_down: |
| [tests/test\_contentsmanager.py](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jb250ZW50c21hbmFnZXIucHk=) | `98.19% <0%> (-1.3%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/mwouts/jupytext/pull/250/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=footer). Last update [2d97aaf...636ea1a](https://codecov.io/gh/mwouts/jupytext/pull/250?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index ab06f0bd..db331f53 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -44,6 +44,8 @@ install:
 before_script:
   # stop the build if there are Python syntax errors or undefined names
   - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
+  # all Python files should follow PEP8 (except some notebooks, see setup.cfg)
+  - flake8 jupytext tests
   # exit-zero treats all errors as warnings.  The GitHub editor is 127 chars wide
   - flake8 . --count --exit-zero --max-complexity=10 --statistics
 script:
diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py
index 919a9f13..3f227942 100644
--- a/jupytext/contentsmanager.py
+++ b/jupytext/contentsmanager.py
@@ -231,7 +231,7 @@ class TextFileContentsManager(LargeFileManager, Configurable):
             jupytext_formats = long_form_multiple_formats(jupytext_formats, metadata)
 
             # Set preferred formats if not format name is given yet
-            jupytext_formats = [preferred_format(fmt, self.preferred_jupytext_formats_save) for fmt in jupytext_formats]
+            jupytext_formats = [preferred_format(f, self.preferred_jupytext_formats_save) for f in jupytext_formats]
 
             base, fmt = find_base_path_and_format(path, jupytext_formats)
             self.update_paired_notebooks(path, fmt, jupytext_formats)
diff --git a/jupytext/kernels.py b/jupytext/kernels.py
index f0ceb5f6..60d3529e 100644
--- a/jupytext/kernels.py
+++ b/jupytext/kernels.py
@@ -14,7 +14,6 @@ except ImportError as err:
 
         return local_function
 
-
     find_kernel_specs = raise_error(err)
     get_kernel_spec = raise_error(err)
 
diff --git a/jupytext/languages.py b/jupytext/languages.py
index 4af3cf86..4aaee0c5 100644
--- a/jupytext/languages.py
+++ b/jupytext/languages.py
@@ -15,7 +15,9 @@ _SCRIPT_EXTENSIONS = {'.py': {'language': 'python', 'comment': '#'},
                       '.sh': {'language': 'bash', 'comment': '#'},
                       '.q': {'language': 'q', 'comment': '/'},
                       '.m': {'language': 'matlab', 'comment': '%'},
-                      '.pro': {'language': 'idl', 'comment': ';'}}
+                      '.pro': {'language': 'idl', 'comment': ';'},
+                      '.js': {'language': 'javascript', 'comment': '//'},
+                      '.ts': {'language': 'typescript', 'comment': '//'}}
 
 _COMMENT_CHARS = [_SCRIPT_EXTENSIONS[ext]['comment'] for ext in _SCRIPT_EXTENSIONS if
                   _SCRIPT_EXTENSIONS[ext]['comment'] != '#']
diff --git a/jupytext/metadata_filter.py b/jupytext/metadata_filter.py
index d54ca9aa..989f5676 100644
--- a/jupytext/metadata_filter.py
+++ b/jupytext/metadata_filter.py
@@ -38,7 +38,7 @@ def metadata_filter_as_dict(metadata_config):
         if 'all' in metadata_config[section]:
             metadata_config[section] = 'all'
         else:
-            metadata_config[section] = [key for key in metadata_config[section] if key]
+            metadata_config[section] = [k for k in metadata_config[section] if k]
 
     return metadata_config
 
diff --git a/jupytext/paired_paths.py b/jupytext/paired_paths.py
index 8be01bfd..4fe2af3d 100644
--- a/jupytext/paired_paths.py
+++ b/jupytext/paired_paths.py
@@ -117,7 +117,7 @@ def paired_paths(main_path, fmt, formats):
 
     # Is there a format that matches the main path?
     base = base_path(main_path, fmt)
-    paths = [full_path(base, fmt) for fmt in formats]
+    paths = [full_path(base, f) for f in formats]
 
     if main_path not in paths:
         raise InconsistentPath(u"Paired paths '{}' do not include the current notebook path '{}'. "
diff --git a/setup.cfg b/setup.cfg
index 01502fae..056f247f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -17,4 +17,4 @@ exclude_lines =
 
 [flake8]
 max-line-length=127
-exclude=*hydrogen/*
+exclude=tests/notebooks/*


Test patch:
diff --git a/tests/notebooks/ipynb_js/ijavascript.ipynb b/tests/notebooks/ipynb_js/ijavascript.ipynb
new file mode 100644
index 00000000..4151c993
--- /dev/null
+++ b/tests/notebooks/ipynb_js/ijavascript.ipynb
@@ -0,0 +1,249 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## A notebook that uses IJavascript kernel"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "let x = 5;\n",
+    "const y = 6;\n",
+    "var z = 10;"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x + y;"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "function add(num1, num2) {\n",
+    "    return num1 + num2\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "add(x, y);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "const arrowAdd = (num1, num2) => num1 + num2;"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "arrowAdd(x, y);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "const myCar = {\n",
+    "    color: \"blue\",\n",
+    "    weight: 850,\n",
+    "    model: \"fiat\",\n",
+    "    start: () => \"car started!\",\n",
+    "    doors: [1,2,3,4]\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "color: blue\n"
+     ]
+    }
+   ],
+   "source": [
+    "console.log(\"color:\", myCar.color);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "start: car started!\n"
+     ]
+    }
+   ],
+   "source": [
+    "console.log(\"start:\", myCar.start());"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "I'm door 1\n",
+      "I'm door 2\n",
+      "I'm door 3\n",
+      "I'm door 4\n"
+     ]
+    }
+   ],
+   "source": [
+    "for (let door of myCar.doors) {\n",
+    "    console.log(\"I'm door\", door)\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{ color: 'blue',\n",
+       "  weight: 850,\n",
+       "  model: 'fiat',\n",
+       "  start: [Function: start],\n",
+       "  doors: [ 1, 2, 3, 4 ] }"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "myCar;"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class User {\n",
+    "  constructor(name){\n",
+    "    this.name = name;\n",
+    "  }\n",
+    "  sayHello(){\n",
+    "    return \"Hello, I'm \" + this.name;\n",
+    "  }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"Hello, I'm John\""
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "let John = new User(\"John\");\n",
+    "John.sayHello();"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Javascript (Node.js)",
+   "language": "javascript",
+   "name": "javascript"
+  },
+  "language_info": {
+   "file_extension": ".js",
+   "mimetype": "application/javascript",
+   "name": "javascript",
+   "version": "11.14.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/notebooks/ipynb_ts/itypescript.ipynb b/tests/notebooks/ipynb_ts/itypescript.ipynb
new file mode 100644
index 00000000..d3d599b2
--- /dev/null
+++ b/tests/notebooks/ipynb_ts/itypescript.ipynb
@@ -0,0 +1,124 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "hi\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "let x: number = 5;\n",
+    "const y: number = 6;\n",
+    "var z: string = \"hi\";\n",
+    "console.log(z);\n",
+    "x + y;"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "function add(num1: number, num2: number): number {\n",
+    "    return num1 + num2\n",
+    "}\n",
+    "add(x, y);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "11"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "const arrowAdd = (num1: number, num2: number) => num1 + num2;\n",
+    "arrowAdd(x, y);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"Hello, I'm John\""
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "class User {\n",
+    "  constructor(private name: string) {\n",
+    "    this.name = name;\n",
+    "  }\n",
+    "  sayHello(): string {\n",
+    "    return \"Hello, I'm \" + this.name;\n",
+    "  }\n",
+    "}\n",
+    "let John: User;\n",
+    "John = new User(\"John\");\n",
+    "John.sayHello();"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Typescript 3.5",
+   "language": "typescript",
+   "name": "typescript"
+  },
+  "language_info": {
+   "file_extension": ".ts",
+   "mimetype": "application/x-typescript",
+   "name": "typescript",
+   "version": "3.5.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/ijavascript.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/ijavascript.Rmd
new file mode 100644
index 00000000..2aa7acb6
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_Rmd/ijavascript.Rmd
@@ -0,0 +1,81 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Javascript (Node.js)
+    language: javascript
+    name: javascript
+---
+
+## A notebook that uses IJavascript kernel
+
+```{javascript}
+let x = 5;
+const y = 6;
+var z = 10;
+```
+
+```{javascript}
+x + y;
+```
+
+```{javascript}
+function add(num1, num2) {
+    return num1 + num2
+}
+```
+
+```{javascript}
+add(x, y);
+```
+
+```{javascript}
+const arrowAdd = (num1, num2) => num1 + num2;
+```
+
+```{javascript}
+arrowAdd(x, y);
+```
+
+```{javascript}
+const myCar = {
+    color: "blue",
+    weight: 850,
+    model: "fiat",
+    start: () => "car started!",
+    doors: [1,2,3,4]
+}
+```
+
+```{javascript}
+console.log("color:", myCar.color);
+```
+
+```{javascript}
+console.log("start:", myCar.start());
+```
+
+```{javascript}
+for (let door of myCar.doors) {
+    console.log("I'm door", door)
+}
+```
+
+```{javascript}
+myCar;
+```
+
+```{javascript}
+class User {
+  constructor(name){
+    this.name = name;
+  }
+  sayHello(){
+    return "Hello, I'm " + this.name;
+  }
+}
+```
+
+```{javascript}
+let John = new User("John");
+John.sayHello();
+```
diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/itypescript.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/itypescript.Rmd
new file mode 100644
index 00000000..a44d6bdb
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_Rmd/itypescript.Rmd
@@ -0,0 +1,41 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Typescript 3.5
+    language: typescript
+    name: typescript
+---
+
+```{typescript}
+let x: number = 5;
+const y: number = 6;
+var z: string = "hi";
+console.log(z);
+x + y;
+```
+
+```{typescript}
+function add(num1: number, num2: number): number {
+    return num1 + num2
+}
+add(x, y);
+```
+
+```{typescript}
+const arrowAdd = (num1: number, num2: number) => num1 + num2;
+arrowAdd(x, y);
+```
+
+```{typescript}
+class User {
+  constructor(private name: string) {
+    this.name = name;
+  }
+  sayHello(): string {
+    return "Hello, I'm " + this.name;
+  }
+}
+let John: User;
+John = new User("John");
+John.sayHello();
+```
diff --git a/tests/notebooks/mirror/ipynb_to_md/ijavascript.md b/tests/notebooks/mirror/ipynb_to_md/ijavascript.md
new file mode 100644
index 00000000..bbd4f650
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_md/ijavascript.md
@@ -0,0 +1,81 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Javascript (Node.js)
+    language: javascript
+    name: javascript
+---
+
+## A notebook that uses IJavascript kernel
+
+```javascript
+let x = 5;
+const y = 6;
+var z = 10;
+```
+
+```javascript
+x + y;
+```
+
+```javascript
+function add(num1, num2) {
+    return num1 + num2
+}
+```
+
+```javascript
+add(x, y);
+```
+
+```javascript
+const arrowAdd = (num1, num2) => num1 + num2;
+```
+
+```javascript
+arrowAdd(x, y);
+```
+
+```javascript
+const myCar = {
+    color: "blue",
+    weight: 850,
+    model: "fiat",
+    start: () => "car started!",
+    doors: [1,2,3,4]
+}
+```
+
+```javascript
+console.log("color:", myCar.color);
+```
+
+```javascript
+console.log("start:", myCar.start());
+```
+
+```javascript
+for (let door of myCar.doors) {
+    console.log("I'm door", door)
+}
+```
+
+```javascript
+myCar;
+```
+
+```javascript
+class User {
+  constructor(name){
+    this.name = name;
+  }
+  sayHello(){
+    return "Hello, I'm " + this.name;
+  }
+}
+```
+
+```javascript
+let John = new User("John");
+John.sayHello();
+```
diff --git a/tests/notebooks/mirror/ipynb_to_md/itypescript.md b/tests/notebooks/mirror/ipynb_to_md/itypescript.md
new file mode 100644
index 00000000..d3921430
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_md/itypescript.md
@@ -0,0 +1,41 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Typescript 3.5
+    language: typescript
+    name: typescript
+---
+
+```typescript
+let x: number = 5;
+const y: number = 6;
+var z: string = "hi";
+console.log(z);
+x + y;
+```
+
+```typescript
+function add(num1: number, num2: number): number {
+    return num1 + num2
+}
+add(x, y);
+```
+
+```typescript
+const arrowAdd = (num1: number, num2: number) => num1 + num2;
+arrowAdd(x, y);
+```
+
+```typescript
+class User {
+  constructor(private name: string) {
+    this.name = name;
+  }
+  sayHello(): string {
+    return "Hello, I'm " + this.name;
+  }
+}
+let John: User;
+John = new User("John");
+John.sayHello();
+```
diff --git a/tests/notebooks/mirror/ipynb_to_pandoc/ijavascript.md b/tests/notebooks/mirror/ipynb_to_pandoc/ijavascript.md
new file mode 100644
index 00000000..92f54140
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_pandoc/ijavascript.md
@@ -0,0 +1,111 @@
+---
+jupyter:
+  kernelspec:
+    display_name: 'Javascript (Node.js)'
+    language: javascript
+    name: javascript
+  nbformat: 4
+  nbformat_minor: 2
+---
+
+::: {.cell .markdown}
+## A notebook that uses IJavascript kernel
+:::
+
+::: {.cell .code}
+``` {.javascript}
+let x = 5;
+const y = 6;
+var z = 10;
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+x + y;
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+function add(num1, num2) {
+    return num1 + num2
+}
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+add(x, y);
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+const arrowAdd = (num1, num2) => num1 + num2;
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+arrowAdd(x, y);
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+const myCar = {
+    color: "blue",
+    weight: 850,
+    model: "fiat",
+    start: () => "car started!",
+    doors: [1,2,3,4]
+}
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+console.log("color:", myCar.color);
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+console.log("start:", myCar.start());
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+for (let door of myCar.doors) {
+    console.log("I'm door", door)
+}
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+myCar;
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+class User {
+  constructor(name){
+    this.name = name;
+  }
+  sayHello(){
+    return "Hello, I'm " + this.name;
+  }
+}
+```
+:::
+
+::: {.cell .code}
+``` {.javascript}
+let John = new User("John");
+John.sayHello();
+```
+:::
diff --git a/tests/notebooks/mirror/ipynb_to_pandoc/itypescript.md b/tests/notebooks/mirror/ipynb_to_pandoc/itypescript.md
new file mode 100644
index 00000000..e1b87426
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_pandoc/itypescript.md
@@ -0,0 +1,51 @@
+---
+jupyter:
+  kernelspec:
+    display_name: 'Typescript 3.5'
+    language: typescript
+    name: typescript
+  nbformat: 4
+  nbformat_minor: 2
+---
+
+::: {.cell .code}
+``` {.typescript}
+let x: number = 5;
+const y: number = 6;
+var z: string = "hi";
+console.log(z);
+x + y;
+```
+:::
+
+::: {.cell .code}
+``` {.typescript}
+function add(num1: number, num2: number): number {
+    return num1 + num2
+}
+add(x, y);
+```
+:::
+
+::: {.cell .code}
+``` {.typescript}
+const arrowAdd = (num1: number, num2: number) => num1 + num2;
+arrowAdd(x, y);
+```
+:::
+
+::: {.cell .code}
+``` {.typescript}
+class User {
+  constructor(private name: string) {
+    this.name = name;
+  }
+  sayHello(): string {
+    return "Hello, I'm " + this.name;
+  }
+}
+let John: User;
+John = new User("John");
+John.sayHello();
+```
+:::
diff --git a/tests/notebooks/mirror/ipynb_to_percent/ijavascript.js b/tests/notebooks/mirror/ipynb_to_percent/ijavascript.js
new file mode 100644
index 00000000..63eed056
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_percent/ijavascript.js
@@ -0,0 +1,69 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Javascript (Node.js)
+//     language: javascript
+//     name: javascript
+// ---
+
+// %% [markdown]
+// ## A notebook that uses IJavascript kernel
+
+// %%
+let x = 5;
+const y = 6;
+var z = 10;
+
+// %%
+x + y;
+
+// %%
+function add(num1, num2) {
+    return num1 + num2
+}
+
+// %%
+add(x, y);
+
+// %%
+const arrowAdd = (num1, num2) => num1 + num2;
+
+// %%
+arrowAdd(x, y);
+
+// %%
+const myCar = {
+    color: "blue",
+    weight: 850,
+    model: "fiat",
+    start: () => "car started!",
+    doors: [1,2,3,4]
+}
+
+// %%
+console.log("color:", myCar.color);
+
+// %%
+console.log("start:", myCar.start());
+
+// %%
+for (let door of myCar.doors) {
+    console.log("I'm door", door)
+}
+
+// %%
+myCar;
+
+// %%
+class User {
+  constructor(name){
+    this.name = name;
+  }
+  sayHello(){
+    return "Hello, I'm " + this.name;
+  }
+}
+
+// %%
+let John = new User("John");
+John.sayHello();
diff --git a/tests/notebooks/mirror/ipynb_to_percent/itypescript.ts b/tests/notebooks/mirror/ipynb_to_percent/itypescript.ts
new file mode 100644
index 00000000..ce28cd7c
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_percent/itypescript.ts
@@ -0,0 +1,37 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Typescript 3.5
+//     language: typescript
+//     name: typescript
+// ---
+
+// %%
+let x: number = 5;
+const y: number = 6;
+var z: string = "hi";
+console.log(z);
+x + y;
+
+// %%
+function add(num1: number, num2: number): number {
+    return num1 + num2
+}
+add(x, y);
+
+// %%
+const arrowAdd = (num1: number, num2: number) => num1 + num2;
+arrowAdd(x, y);
+
+// %%
+class User {
+  constructor(private name: string) {
+    this.name = name;
+  }
+  sayHello(): string {
+    return "Hello, I'm " + this.name;
+  }
+}
+let John: User;
+John = new User("John");
+John.sayHello();
diff --git a/tests/notebooks/mirror/ipynb_to_script/ijavascript.js b/tests/notebooks/mirror/ipynb_to_script/ijavascript.js
new file mode 100644
index 00000000..feb5cf00
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_script/ijavascript.js
@@ -0,0 +1,55 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Javascript (Node.js)
+//     language: javascript
+//     name: javascript
+// ---
+
+// ## A notebook that uses IJavascript kernel
+
+let x = 5;
+const y = 6;
+var z = 10;
+
+x + y;
+
+function add(num1, num2) {
+    return num1 + num2
+}
+
+add(x, y);
+
+const arrowAdd = (num1, num2) => num1 + num2;
+
+arrowAdd(x, y);
+
+const myCar = {
+    color: "blue",
+    weight: 850,
+    model: "fiat",
+    start: () => "car started!",
+    doors: [1,2,3,4]
+}
+
+console.log("color:", myCar.color);
+
+console.log("start:", myCar.start());
+
+for (let door of myCar.doors) {
+    console.log("I'm door", door)
+}
+
+myCar;
+
+class User {
+  constructor(name){
+    this.name = name;
+  }
+  sayHello(){
+    return "Hello, I'm " + this.name;
+  }
+}
+
+let John = new User("John");
+John.sayHello();
diff --git a/tests/notebooks/mirror/ipynb_to_script/itypescript.ts b/tests/notebooks/mirror/ipynb_to_script/itypescript.ts
new file mode 100644
index 00000000..ae59dfb9
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_script/itypescript.ts
@@ -0,0 +1,33 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Typescript 3.5
+//     language: typescript
+//     name: typescript
+// ---
+
+let x: number = 5;
+const y: number = 6;
+var z: string = "hi";
+console.log(z);
+x + y;
+
+function add(num1: number, num2: number): number {
+    return num1 + num2
+}
+add(x, y);
+
+const arrowAdd = (num1: number, num2: number) => num1 + num2;
+arrowAdd(x, y);
+
+class User {
+  constructor(private name: string) {
+    this.name = name;
+  }
+  sayHello(): string {
+    return "Hello, I'm " + this.name;
+  }
+}
+let John: User;
+John = new User("John");
+John.sayHello();
diff --git a/tests/test_mirror.py b/tests/test_mirror.py
index 168ae113..67601787 100644
--- a/tests/test_mirror.py
+++ b/tests/test_mirror.py
@@ -290,3 +290,23 @@ def test_ipynb_to_pro_percent(nb_file):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb', skip='(functional|Notebook with|flavors|invalid)'))
 def test_ipynb_to_pandoc(nb_file):
     assert_conversion_same_as_mirror(nb_file, 'md:pandoc', 'ipynb_to_pandoc')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_js'))
+def test_ipynb_to_js(nb_file):
+    assert_conversion_same_as_mirror(nb_file, 'js', 'ipynb_to_script')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_js'))
+def test_ipynb_to_js_percent(nb_file):
+    assert_conversion_same_as_mirror(nb_file, 'js:percent', 'ipynb_to_percent')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_ts'))
+def test_ipynb_to_ts(nb_file):
+    assert_conversion_same_as_mirror(nb_file, 'ts', 'ipynb_to_script')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_ts'))
+def test_ipynb_to_ts_percent(nb_file):
+    assert_conversion_same_as_mirror(nb_file, 'ts:percent', 'ipynb_to_percent')
diff --git a/tests/test_pep8.py b/tests/test_pep8.py
index 72ca6c26..b4ca10f2 100644
--- a/tests/test_pep8.py
+++ b/tests/test_pep8.py
@@ -156,6 +156,7 @@ def f(x):
 @pytest.mark.parametrize('py_file', [py_file for py_file in list_notebooks('../jupytext') + list_notebooks('.') if
                                      py_file.endswith('.py') and 'folding_markers' not in py_file])
 def test_no_metadata_when_py_is_pep8(py_file):
+    """This test assumes that all Python files in the jupytext folder follow PEP8 rules"""
     nb = readf(py_file)
 
     for i, cell in enumerate(nb.cells):