Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F12654102
test.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
test.ts
View Options
import
Koa
,
{
BaseContext
}
from
"koa"
;
import
http
from
"http"
;
import
assert
from
"assert"
;
import
{
promisify
}
from
"util"
;
import
{
FlatTemplatable
,
Templatable
,
tempstream
}
from
"."
;
import
streamToString
from
"./tostring"
;
import
{
PassThrough
}
from
"stream"
;
import
{
prettify
}
from
"./prettify"
;
const
st
=
(
time
:
number
,
cb
:
()
=>
void
)
=>
setTimeout
(
cb
,
time
);
const
sleep
=
promisify
(
st
);
// template`hello ${world}, and ${name}`.pipe(process.stdout);
describe
(
"tempstream"
,
()
=>
{
it
(
"renders properly in the basic case"
,
async
()
=>
{
const
list_items
=
Promise
.
resolve
(
[
"one"
,
"two"
,
"three"
].
map
((
e
)
=>
`<li>
${
e
}
</li>`
)
);
const
title
=
"Changed page title"
;
const
slept
=
sleep
(
100
).
then
(()
=>
"slept"
);
const
result
=
await
streamToString
(
tempstream
`<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>
${
title
}
</title>
</head>
<body>
hello World, I
${
slept
}
.
<ul>
${
list_items
}
</ul>
</body> `
);
assert
.
strictEqual
(
result
,
`<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>Changed page title</title>
</head>
<body>
hello World, I slept.
<ul>
<li>one</li><li>two</li><li>three</li>
</ul>
</body> `
);
});
it
(
"handles stream within a stream"
,
async
()
=>
{
const
item_elements
=
Promise
.
resolve
([
"one"
,
"two"
,
"three"
]);
const
inside_stream
=
tempstream
`Here's a list:
${
item_elements
}
`
;
const
outside_stream
=
tempstream
`<div>
${
inside_stream
}
</div>`
;
const
result
=
await
streamToString
(
outside_stream
);
assert
.
strictEqual
(
result
,
`<div>Here's a list: onetwothree</div>`
);
});
it
(
"handles an array of promises"
,
async
()
=>
{
async
function
process
(
text
:
string
)
{
return
"processed "
+
text
+
"\n"
;
}
const
result
=
await
streamToString
(
tempstream
`
${
[
"a"
,
"b"
,
"c"
].
map
(
process
)
}
`
);
assert
.
strictEqual
(
result
,
`processed a
processed b
processed c
`
);
});
it
(
"handles an array of promises of streams"
,
async
()
=>
{
async
function
process
(
text
:
string
)
{
return
tempstream
`processed
${
text
}
\n`
;
}
const
result
=
await
streamToString
(
tempstream
`
${
[
"a"
,
"b"
,
"c"
].
map
(
process
)
}
`
);
assert
.
strictEqual
(
result
,
`processed a
processed b
processed c
`
);
});
it
(
"handles an array of promises of streams nested within a stream"
,
async
()
=>
{
async
function
process
(
text
:
string
)
{
return
tempstream
`processed
${
text
}
\n`
;
}
const
result
=
await
streamToString
(
tempstream
`PREFIX:
${
tempstream
`
${
[
"a"
,
"b"
,
"c"
].
map
(
process
)
}
`
}
`
);
assert
.
strictEqual
(
result
,
`PREFIX: processed a
processed b
processed c
`
);
});
it
(
"handles an array of delayed promises of streams nested within a stream"
,
async
()
=>
{
async
function
process
(
text
:
string
)
{
await
sleep
(
100
);
return
tempstream
`processed
${
text
}
\n`
;
}
const
result
=
await
streamToString
(
tempstream
`PREFIX:
${
tempstream
`
${
[
"a"
,
"b"
,
"c"
].
map
(
process
)
}
`
}
`
);
assert
.
strictEqual
(
result
,
`PREFIX: processed a
processed b
processed c
`
);
});
it
(
"sends the first byte as soon as possible when dealing with delayed promises of streams nested within a stream"
,
async
()
=>
{
async
function
process
(
text
:
string
)
{
await
sleep
(
100
);
return
tempstream
`processed
${
text
}
\n`
;
}
const
stream_start_ts
=
Date
.
now
();
const
stream
=
tempstream
`PREFIX:
${
tempstream
`
${
[
"a"
,
"b"
,
"c"
].
map
(
process
)
}
`
}
`
;
let
result
=
""
;
let
ttfb
:
number
|
null
=
null
;
await
new
Promise
((
resolve
)
=>
{
stream
.
on
(
"data"
,
(
newdata
)
=>
{
result
+=
newdata
.
toString
();
if
(
ttfb
===
null
)
{
ttfb
=
Date
.
now
()
-
stream_start_ts
;
}
});
stream
.
on
(
"end"
,
()
=>
resolve
(
result
));
});
assert
(
ttfb
!==
null
&&
ttfb
<
10
);
assert
.
strictEqual
(
result
,
`PREFIX: processed a
processed b
processed c
`
);
});
it
(
"properly renders `(Templatable | Promise<Templatable>)[]`"
,
async
()
=>
{
const
items
=
[
tempstream
`hello `
,
tempstream
`world `
,
Promise
.
resolve
(
tempstream
`I am `
)
as
Promise
<
Templatable
>
,
"testing"
,
];
const
result
=
await
streamToString
(
tempstream
`
${
items
as
Templatable
}
`
);
assert
.
strictEqual
(
result
,
`hello world I am testing`
);
});
it
(
"properly renders `Promise<FlatTemplatable[]>`"
,
async
()
=>
{
const
items
=
Promise
.
resolve
([
tempstream
`hello `
,
tempstream
`world `
,
Promise
.
resolve
(
tempstream
`I am `
)
as
Promise
<
FlatTemplatable
>
,
"testing"
,
]
as
FlatTemplatable
[]);
const
result
=
await
streamToString
(
tempstream
`
${
items
}
`
);
assert
.
strictEqual
(
result
,
`hello world I am testing`
);
});
it
(
"properly closes the stream to prevent premature ending when sending over HTTP"
,
async
()
=>
{
const
app
=
new
Koa
();
async
function
generateOptions
()
{
const
ret
=
{}
as
Record
<
string
,
unknown
>
;
for
(
let
i
=
0
;
i
<=
4000
;
i
++
)
{
ret
[
i
.
toString
()]
=
i
;
}
return
ret
;
}
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
ctx
.
body
=
tempstream
/* HTML */
`<select>
${
Promise
.
resolve
(
generateOptions
()).
then
((
options
)
=>
Object
.
entries
(
options
).
map
(
([
value
])
=>
`<option value="
${
value
}
"></option>`
)
)
}
</select>`
;
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
const
response
=
(
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
}))
as
string
;
server
.
close
();
assert
(
response
.
endsWith
(
"</select>"
),
"Response should be transmitted in full, and not cut before it ends"
);
});
it
(
"Allows to catch an error thrown by an awaited promise within the template"
,
async
()
=>
{
const
app
=
new
Koa
();
let
caught
=
false
;
app
.
use
(
async
(
ctx
,
next
)
=>
{
await
next
();
ctx
.
body
.
waitUntilFinished
().
then
(()
=>
{
if
(
ctx
.
body
.
has_errors
)
{
caught
=
true
;
}
});
});
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
ctx
.
body
=
tempstream
/* HTML */
`<div>
${
sleep
(
100
).
then
(()
=>
{
throw
new
Error
(
"SOME ERROR!"
);
}
)}
</div>`
;
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
(
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
}))
as
string
;
server
.
close
();
assert
(
caught
);
});
it
(
"Allows to await the finishing of the stream"
,
async
()
=>
{
const
app
=
new
Koa
();
let
got_success_signal
=
false
;
app
.
use
(
async
(
ctx
,
next
)
=>
{
await
next
();
ctx
.
body
.
waitUntilFinished
().
then
(()
=>
{
got_success_signal
=
true
;
});
});
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
ctx
.
body
=
tempstream
/* HTML */
`<div>
${
sleep
(
100
).
then
(()
=>
{
return
"Everything's ok"
;
}
)}
</div>`
;
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
(
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
}))
as
string
;
server
.
close
();
assert
(
got_success_signal
);
});
it
(
"doesn't leave uncaught promises within nested streams"
,
async
()
=>
{
const
app
=
new
Koa
();
let
got_error_signal
=
false
;
app
.
use
(
async
(
ctx
,
next
)
=>
{
await
next
();
ctx
.
body
.
waitUntilFinished
().
then
(()
=>
{
got_error_signal
=
true
;
});
});
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
const
sleep1
=
sleep
(
100
).
then
(()
=>
{
return
"Everything's ok"
;
});
const
sleep2
=
sleep
(
200
).
then
(()
=>
{
throw
new
Error
(
"SOME ERROR"
);
});
ctx
.
body
=
tempstream
/* HTML */
`
${
tempstream
`<div>
${
sleep1
}
,
${
sleep2
}
</div>`
}
`
;
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
(
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
}))
as
string
;
server
.
close
();
assert
(
got_error_signal
);
});
it
(
"doesn't leave uncaught rejections for promises within the stream"
,
async
()
=>
{
let
got_unhandled_rejection
=
false
;
let
got_error_signal
=
false
;
const
unhandled_listener
=
(
reason
:
Error
,
_
:
unknown
)
=>
{
console
.
log
(
"GOT UNHANDLED REJECTION!"
,
reason
.
message
);
got_unhandled_rejection
=
true
;
};
process
.
on
(
"unhandledRejection"
,
unhandled_listener
)
.
on
(
"uncaughtException"
,
unhandled_listener
);
const
app
=
new
Koa
();
app
.
use
(
async
(
ctx
,
next
)
=>
{
// this approach has the downside of blocking TTFB until the stream
// is finished, kind of defeating the purpose of using streams in
// the first place. BUT! A big TODO would be to not close the
// new_stream when the pipe ends
// (https://nodejs.org/api/stream.html#readablepipedestination-options)
// and not await the waitUntilFinishedPromise, but just append some
// error handling directives to the new stream on `waitUntilFinished().then()`.
await
next
();
if
(
ctx
.
body
.
waitUntilFinished
)
{
const
original_stream
=
ctx
.
body
;
const
new_stream
=
new
PassThrough
();
ctx
.
body
=
new_stream
;
original_stream
.
pipe
(
new_stream
);
await
original_stream
.
waitUntilFinished
();
if
(
original_stream
.
has_errors
)
{
got_error_signal
=
true
;
}
}
});
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
const
makeResponse
=
async
()
=>
{
function
simpleMessage
(
t
:
FlatTemplatable
)
:
FlatTemplatable
{
return
tempstream
`t:
${
t
}
`
;
}
async
function
getAd
()
{
await
sleep
(
200
);
throw
new
Error
(
"syntetic ERROR!"
);
}
const
ad
=
getAd
();
return
simpleMessage
(
tempstream
`Some text before:
${
ad
.
then
(
(
_
)
=>
"success"
)
}
; and then some text after`
);
};
ctx
.
body
=
await
makeResponse
();
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
(
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
}))
as
string
;
server
.
close
();
assert
(
!
got_unhandled_rejection
);
assert
(
got_error_signal
);
process
.
removeListener
(
"unhandledRejection"
,
unhandled_listener
)
.
removeListener
(
"uncaughtException"
,
unhandled_listener
);
});
it
.
skip
(
"doesn't leave unaught promise rejections from promises rejected before the stream starts"
,
async
()
=>
{
// the difference from the test above is that we're letting the promise
// reject before tempstream has a chance to set up any error listeners
// passing this test might actually be impossible. Maybe some eslint
// rule is able to catch that?
// Instead, tempstream will set up unhandledRejection listeners to
// explain in the console what went wrong.
// unskip this test scenario to test this behavior
let
got_unhandled_rejection
=
false
;
let
got_error_signal
=
false
;
const
unhandled_listener
=
(
reason
:
Error
,
_
:
unknown
)
=>
{
console
.
log
(
"GOT UNHANDLED REJECTION!"
,
reason
.
message
);
got_unhandled_rejection
=
true
;
};
process
.
on
(
"unhandledRejection"
,
unhandled_listener
)
.
on
(
"uncaughtException"
,
unhandled_listener
);
const
app
=
new
Koa
();
app
.
use
(
async
(
ctx
,
next
)
=>
{
// this approach has the downside of blocking TTFB until the stream
// is finished, kind of defeating the purpose of using streams in
// the first place. BUT! A big TODO would be to not close the
// new_stream when the pipe ends
// (https://nodejs.org/api/stream.html#readablepipedestination-options)
// and not await the waitUntilFinishedPromise, but just append some
// error handling directives to the new stream on `waitUntilFinished().then()`.
await
next
();
if
(
ctx
.
body
.
waitUntilFinished
)
{
const
original_stream
=
ctx
.
body
;
const
new_stream
=
new
PassThrough
();
ctx
.
body
=
new_stream
;
original_stream
.
pipe
(
new_stream
);
await
original_stream
.
waitUntilFinished
();
if
(
original_stream
.
has_errors
)
{
got_error_signal
=
true
;
}
}
});
app
.
use
(
async
(
ctx
:
BaseContext
)
=>
{
const
makeResponse
=
async
()
=>
{
function
simpleMessage
(
t
:
FlatTemplatable
)
:
FlatTemplatable
{
return
tempstream
`t:
${
t
}
`
;
}
async
function
getAd
()
{
await
sleep
(
100
);
throw
new
Error
(
"syntetic ERROR!"
);
}
const
ad
=
getAd
();
await
sleep
(
200
);
// <=============== here's the difference to the other, similar test
return
simpleMessage
(
tempstream
`Some text before:
${
ad
.
then
(
()
=>
"success"
)
}
; and then some text after`
);
};
ctx
.
body
=
await
makeResponse
();
});
const
port
=
3787
;
const
server
=
app
.
listen
(
port
);
await
sleep
(
100
);
await
new
Promise
((
resolve
)
=>
{
http
.
get
(
`http://127.0.0.1:
${
port
}
`
,
{},
(
res
)
=>
{
resolve
(
streamToString
(
res
));
// res.on("end", resolve);
});
});
server
.
close
();
assert
(
!
got_unhandled_rejection
);
assert
(
got_error_signal
);
process
.
removeListener
(
"unhandledRejection"
,
unhandled_listener
)
.
removeListener
(
"uncaughtException"
,
unhandled_listener
);
});
it
(
"handles null values properly"
,
async
()
=>
{
const
stream
=
tempstream
`some
${
null
}
value`
;
const
result
=
await
streamToString
(
stream
);
assert
.
strictEqual
(
result
,
"some value"
);
});
it
(
"handles number values properly"
,
async
()
=>
{
const
stream
=
tempstream
`some
${
5
}
value`
;
const
result
=
await
streamToString
(
stream
);
assert
.
strictEqual
(
result
,
"some 5 value"
);
});
it
(
"handles stringifiable values properly"
,
async
()
=>
{
const
object
=
{
toString
:
()
=>
"hehe"
};
const
stream
=
tempstream
`some
${
object
as
any
}
value`
;
const
result
=
await
streamToString
(
stream
);
assert
.
strictEqual
(
result
,
"some hehe value"
);
});
it
(
"handles the number 0 properly"
,
async
()
=>
{
const
stream
=
tempstream
`some
${
0
}
value`
;
const
result
=
await
streamToString
(
stream
);
assert
.
strictEqual
(
result
,
"some 0 value"
);
});
it
(
"attaches error handlers even if they are part of an array"
,
async
()
=>
{
const
stream
=
tempstream
`some
${
[
1
,
2
,
3
,
4
].
map
(
async
(
e
,
i
)
=>
{
await
sleep
(
500
-
100
*
i
);
throw
new
Error
(
"not so fast"
);
}
)}`
;
const
result
=
await
streamToString
(
stream
);
assert
.
strictEqual
(
result
,
"some errorerrorerrorerror"
);
});
});
File Metadata
Details
Attached
Mime Type
text/html
Expires
Fri, Nov 28, 14:49 (2 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1066092
Default Alt Text
test.ts (13 KB)
Attached To
Mode
rSTREAM tempstream
Attached
Detach File
Event Timeline
Log In to Comment