]> git.proxmox.com Git - mirror_novnc.git/blobdiff - tests/test.rfb.js
adds qualityLevel property to RFB class for updating JPEG quality level encoding...
[mirror_novnc.git] / tests / test.rfb.js
index 17320e46921a26b7eb7268cf5fb09c6cb10020be..4e2739358390bdd3caba3689553acc0b028c1858 100644 (file)
@@ -2,7 +2,11 @@ const expect = chai.expect;
 
 import RFB from '../core/rfb.js';
 import Websock from '../core/websock.js';
+import ZStream from "../vendor/pako/lib/zlib/zstream.js";
+import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
 import { encodings } from '../core/encodings.js';
+import { toUnsigned32bit } from '../core/util/int.js';
+import { encodeUTF8 } from '../core/util/strings.js';
 
 import FakeWebSocket from './fake.websocket.js';
 
@@ -48,6 +52,35 @@ function pushString(arr, string) {
     }
 }
 
+function deflateWithSize(data) {
+    // Adds the size of the string in front before deflating
+
+    let unCompData = [];
+    unCompData.push((data.length >> 24) & 0xFF,
+                    (data.length >> 16) & 0xFF,
+                    (data.length >>  8) & 0xFF,
+                    (data.length & 0xFF));
+
+    for (let i = 0; i < data.length; i++) {
+        unCompData.push(data.charCodeAt(i));
+    }
+
+    let strm = new ZStream();
+    let chunkSize = 1024 * 10 * 10;
+    strm.output = new Uint8Array(chunkSize);
+    deflateInit(strm, 5);
+
+    strm.input = unCompData;
+    strm.avail_in = strm.input.length;
+    strm.next_in = 0;
+    strm.next_out = 0;
+    strm.avail_out = chunkSize;
+
+    deflate(strm, 3);
+
+    return new Uint8Array(strm.output.buffer, 0, strm.next_out);
+}
+
 describe('Remote Frame Buffer Protocol Client', function () {
     let clock;
     let raf;
@@ -291,12 +324,39 @@ describe('Remote Frame Buffer Protocol Client', function () {
         });
 
         describe('#clipboardPasteFrom', function () {
-            it('should send the given text in a paste event', function () {
-                const expected = {_sQ: new Uint8Array(11), _sQlen: 0,
-                                  _sQbufferSize: 11, flush: () => {}};
-                RFB.messages.clientCutText(expected, 'abc');
-                client.clipboardPasteFrom('abc');
-                expect(client._sock).to.have.sent(expected._sQ);
+            describe('Clipboard update handling', function () {
+                beforeEach(function () {
+                    sinon.spy(RFB.messages, 'clientCutText');
+                    sinon.spy(RFB.messages, 'extendedClipboardNotify');
+                });
+
+                afterEach(function () {
+                    RFB.messages.clientCutText.restore();
+                    RFB.messages.extendedClipboardNotify.restore();
+                });
+
+                it('should send the given text in an clipboard update', function () {
+                    client.clipboardPasteFrom('abc');
+
+                    expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+                    expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,
+                                                                               new Uint8Array([97, 98, 99]));
+                });
+
+                it('should send an notify if extended clipboard is supported by server', function () {
+                    // Send our capabilities
+                    let data = [3, 0, 0, 0];
+                    const flags = [0x1F, 0x00, 0x00, 0x01];
+                    let fileSizes = [0x00, 0x00, 0x00, 0x1E];
+
+                    push32(data, toUnsigned32bit(-8));
+                    data = data.concat(flags);
+                    data = data.concat(fileSizes);
+                    client._sock._websocket._receive_data(new Uint8Array(data));
+
+                    client.clipboardPasteFrom('extended test');
+                    expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
+                });
             });
 
             it('should flush multiple times for large clipboards', function () {
@@ -2243,7 +2303,7 @@ describe('Remote Frame Buffer Protocol Client', function () {
                     });
 
                     it('should update the cursor when type is alpha', function () {
-                        let data = [0xee, 0x55, 0xff, 0x00, // bgra
+                        let data = [0xee, 0x55, 0xff, 0x00, // rgba
                                     0x00, 0xff, 0x00, 0xff,
                                     0x00, 0xff, 0x00, 0x22,
                                     0x00, 0xff, 0x00, 0x22,
@@ -2261,12 +2321,12 @@ describe('Remote Frame Buffer Protocol Client', function () {
                             push8(rect, data[i]);
                         }
 
-                        let expected_rgba = [0xff, 0x55, 0xee, 0x00,
+                        let expected_rgba = [0xee, 0x55, 0xff, 0x00,
                                              0x00, 0xff, 0x00, 0xff,
                                              0x00, 0xff, 0x00, 0x22,
                                              0x00, 0xff, 0x00, 0x22,
                                              0x00, 0xff, 0x00, 0x22,
-                                             0xff, 0x00, 0x00, 0xee];
+                                             0x00, 0x00, 0xff, 0xee];
 
                         send_fbu_msg([{ x: hotx, y: hoty,
                                         width: w, height: h,
@@ -2336,17 +2396,249 @@ describe('Remote Frame Buffer Protocol Client', function () {
             });
         });
 
-        it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
-            const expected_str = 'cheese!';
-            const data = [3, 0, 0, 0];
-            push32(data, expected_str.length);
-            for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
-            const spy = sinon.spy();
-            client.addEventListener("clipboard", spy);
+        describe('Normal Clipboard Handling Receive', function () {
+            it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
+                const expected_str = 'cheese!';
+                const data = [3, 0, 0, 0];
+                push32(data, expected_str.length);
+                for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
+                const spy = sinon.spy();
+                client.addEventListener("clipboard", spy);
+
+                client._sock._websocket._receive_data(new Uint8Array(data));
+                expect(spy).to.have.been.calledOnce;
+                expect(spy.args[0][0].detail.text).to.equal(expected_str);
+            });
+        });
+
+        describe('Extended clipboard Handling', function () {
+
+            describe('Extended clipboard initialization', function () {
+                beforeEach(function () {
+                    sinon.spy(RFB.messages, 'extendedClipboardCaps');
+                });
+
+                afterEach(function () {
+                    RFB.messages.extendedClipboardCaps.restore();
+                });
+
+                it('should update capabilities when receiving a Caps message', function () {
+                    let data = [3, 0, 0, 0];
+                    const flags = [0x1F, 0x00, 0x00, 0x03];
+                    let fileSizes = [0x00, 0x00, 0x00, 0x1E,
+                                     0x00, 0x00, 0x00, 0x3C];
+
+                    push32(data, toUnsigned32bit(-12));
+                    data = data.concat(flags);
+                    data = data.concat(fileSizes);
+                    client._sock._websocket._receive_data(new Uint8Array(data));
+
+                    // Check that we give an response caps when we receive one
+                    expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
+
+                    // FIXME: Can we avoid checking internal variables?
+                    expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
+                    expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
+                    expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
+                    expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
+                });
+
+
+            });
+
+            describe('Extended Clipboard Handling Receive', function () {
+
+                beforeEach(function () {
+                    // Send our capabilities
+                    let data = [3, 0, 0, 0];
+                    const flags = [0x1F, 0x00, 0x00, 0x01];
+                    let fileSizes = [0x00, 0x00, 0x00, 0x1E];
+
+                    push32(data, toUnsigned32bit(-8));
+                    data = data.concat(flags);
+                    data = data.concat(fileSizes);
+                    client._sock._websocket._receive_data(new Uint8Array(data));
+                });
+
+                describe('Handle Provide', function () {
+                    it('should update clipboard with correct Unicode data from a Provide message', function () {
+                        let expectedData = "Aå漢字!";
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x10, 0x00, 0x00, 0x01];
+
+                        /* The size 10 (utf8 encoded string size) and the
+                        string "Aå漢字!" utf8 encoded and deflated. */
+                        let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
+                                            188, 244, 217, 158, 69, 79, 215,
+                                            78, 87, 4, 0, 35, 207, 6, 66];
+
+                        // How much data we are sending.
+                        push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
+
+                        data = data.concat(flags);
+                        data = data.concat(deflatedData);
+
+                        const spy = sinon.spy();
+                        client.addEventListener("clipboard", spy);
+
+                        client._sock._websocket._receive_data(new Uint8Array(data));
+                        expect(spy).to.have.been.calledOnce;
+                        expect(spy.args[0][0].detail.text).to.equal(expectedData);
+                        client.removeEventListener("clipboard", spy);
+                    });
+
+                    it('should update clipboard with correct escape characters from a Provide message ', function () {
+                        let expectedData = "Oh\nmy!";
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x10, 0x00, 0x00, 0x01];
+
+                        let text = encodeUTF8("Oh\r\nmy!\0");
+
+                        let deflatedText = deflateWithSize(text);
+
+                        // How much data we are sending.
+                        push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
+
+                        data = data.concat(flags);
+
+                        let sendData = new Uint8Array(data.length + deflatedText.length);
+                        sendData.set(data);
+                        sendData.set(deflatedText, data.length);
+
+                        const spy = sinon.spy();
+                        client.addEventListener("clipboard", spy);
+
+                        client._sock._websocket._receive_data(sendData);
+                        expect(spy).to.have.been.calledOnce;
+                        expect(spy.args[0][0].detail.text).to.equal(expectedData);
+                        client.removeEventListener("clipboard", spy);
+                    });
+
+                    it('should be able to handle large Provide messages', function () {
+                        // repeat() is not supported in IE so a loop is needed instead
+                        let expectedData = "hello";
+                        for (let i = 1; i <= 100000; i++) {
+                            expectedData += "hello";
+                        }
+
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x10, 0x00, 0x00, 0x01];
+
+                        let text = encodeUTF8(expectedData + "\0");
+
+                        let deflatedText = deflateWithSize(text);
+
+                        // How much data we are sending.
+                        push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
+
+                        data = data.concat(flags);
+
+                        let sendData = new Uint8Array(data.length + deflatedText.length);
+                        sendData.set(data);
+                        sendData.set(deflatedText, data.length);
+
+                        const spy = sinon.spy();
+                        client.addEventListener("clipboard", spy);
+
+                        client._sock._websocket._receive_data(sendData);
+                        expect(spy).to.have.been.calledOnce;
+                        expect(spy.args[0][0].detail.text).to.equal(expectedData);
+                        client.removeEventListener("clipboard", spy);
+                    });
+
+                });
+
+                describe('Handle Notify', function () {
+                    beforeEach(function () {
+                        sinon.spy(RFB.messages, 'extendedClipboardRequest');
+                    });
+
+                    afterEach(function () {
+                        RFB.messages.extendedClipboardRequest.restore();
+                    });
+
+                    it('should make a request with supported formats when receiving a notify message', function () {
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x08, 0x00, 0x00, 0x07];
+                        push32(data, toUnsigned32bit(-4));
+                        data = data.concat(flags);
+                        let expectedData = [0x01];
+
+                        client._sock._websocket._receive_data(new Uint8Array(data));
+
+                        expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
+                        expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
+                    });
+                });
+
+                describe('Handle Peek', function () {
+                    beforeEach(function () {
+                        sinon.spy(RFB.messages, 'extendedClipboardNotify');
+                    });
+
+                    afterEach(function () {
+                        RFB.messages.extendedClipboardNotify.restore();
+                    });
+
+                    it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x04, 0x00, 0x00, 0x00];
+                        push32(data, toUnsigned32bit(-4));
+                        data = data.concat(flags);
+                        let expectedData = [];
+
+                        client._sock._websocket._receive_data(new Uint8Array(data));
+
+                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
+                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
+                    });
+
+                    it('should send a Notify message with supported formats when receiving a Peek', function () {
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x04, 0x00, 0x00, 0x00];
+                        push32(data, toUnsigned32bit(-4));
+                        data = data.concat(flags);
+                        let expectedData = [0x01];
+
+                        // Needed to have clipboard data to read.
+                        // This will trigger a call to Notify, reset history
+                        client.clipboardPasteFrom("HejHej");
+                        RFB.messages.extendedClipboardNotify.resetHistory();
+
+                        client._sock._websocket._receive_data(new Uint8Array(data));
+
+                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
+                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
+                    });
+                });
+
+                describe('Handle Request', function () {
+                    beforeEach(function () {
+                        sinon.spy(RFB.messages, 'extendedClipboardProvide');
+                    });
+
+                    afterEach(function () {
+                        RFB.messages.extendedClipboardProvide.restore();
+                    });
+
+                    it('should send a Provide message with supported formats when receiving a Request', function () {
+                        let data = [3, 0, 0, 0];
+                        const flags = [0x02, 0x00, 0x00, 0x01];
+                        push32(data, toUnsigned32bit(-4));
+                        data = data.concat(flags);
+                        let expectedData = [0x01];
+
+                        client.clipboardPasteFrom("HejHej");
+                        expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
+
+                        client._sock._websocket._receive_data(new Uint8Array(data));
+
+                        expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
+                        expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
+                    });
+                });
+            });
 
-            client._sock._websocket._receive_data(new Uint8Array(data));
-            expect(spy).to.have.been.calledOnce;
-            expect(spy.args[0][0].detail.text).to.equal(expected_str);
         });
 
         it('should fire the bell callback on Bell', function () {
@@ -2573,4 +2865,269 @@ describe('Remote Frame Buffer Protocol Client', function () {
             // error events do nothing
         });
     });
+
+    describe('Quality level setting', function () {
+        const defaultQuality = 6;
+
+        let client;
+
+        beforeEach(function () {
+            client = make_rfb();
+            sinon.spy(RFB.messages, "clientEncodings");
+        });
+
+        afterEach(function () {
+            RFB.messages.clientEncodings.restore();
+        });
+
+        it(`should equal ${defaultQuality} by default`, function () {
+            expect(client._qualityLevel).to.equal(defaultQuality);
+        });
+
+        it('should ignore non-integers when set', function () {
+            client.qualityLevel = '1';
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = 1.5;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = null;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = undefined;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = {};
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+        });
+
+        it('should ignore integers out of range [0, 9]', function () {
+            client.qualityLevel = -1;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = 10;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+        });
+
+        it('should send clientEncodings with new quality value', function () {
+            let newQuality;
+
+            newQuality = 8;
+            client.qualityLevel = newQuality;
+            expect(client.qualityLevel).to.equal(newQuality);
+            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
+            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
+        });
+
+        it('should not send clientEncodings if quality is the same', function () {
+            let newQuality;
+
+            newQuality = 2;
+            client.qualityLevel = newQuality;
+            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
+            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client.qualityLevel = newQuality;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+        });
+
+        it('should not send clientEncodings if not in connected state', function () {
+            let newQuality;
+
+            client._rfb_connection_state = '';
+            newQuality = 2;
+            client.qualityLevel = newQuality;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client._rfb_connection_state = 'connnecting';
+            newQuality = 6;
+            client.qualityLevel = newQuality;
+            expect(RFB.messages.clientEncodings).to.not.have.been.called;
+
+            RFB.messages.clientEncodings.resetHistory();
+
+            client._rfb_connection_state = 'connected';
+            newQuality = 5;
+            client.qualityLevel = newQuality;
+            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
+            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
+        });
+    });
+});
+
+describe('RFB messages', function () {
+    let sock;
+
+    before(function () {
+        FakeWebSocket.replace();
+        sock = new Websock();
+        sock.open();
+    });
+
+    after(function () {
+        FakeWebSocket.restore();
+    });
+
+    describe('Extended Clipboard Handling Send', function () {
+        beforeEach(function () {
+            sinon.spy(RFB.messages, 'clientCutText');
+        });
+
+        afterEach(function () {
+            RFB.messages.clientCutText.restore();
+        });
+
+        it('should call clientCutText with correct Caps data', function () {
+            let formats = {
+                0: 2,
+                2: 4121
+            };
+            let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
+                                               0x00, 0x00, 0x00, 0x02,
+                                               0x00, 0x00, 0x10, 0x19]);
+            let actions = [
+                1 << 24,  // Caps
+                1 << 25,  // Request
+                1 << 26,  // Peek
+                1 << 27,  // Notify
+                1 << 28   // Provide
+            ];
+
+            RFB.messages.extendedClipboardCaps(sock, actions, formats);
+            expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+            expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
+        });
+
+        it('should call clientCutText with correct Request data', function () {
+            let formats = new Uint8Array([0x01]);
+            let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
+
+            RFB.messages.extendedClipboardRequest(sock, formats);
+            expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+            expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
+        });
+
+        it('should call clientCutText with correct Notify data', function () {
+            let formats = new Uint8Array([0x01]);
+            let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
+
+            RFB.messages.extendedClipboardNotify(sock, formats);
+            expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+            expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
+        });
+
+        it('should call clientCutText with correct Provide data', function () {
+            let testText = "Test string";
+            let expectedText = encodeUTF8(testText + "\0");
+
+            let deflatedData =  deflateWithSize(expectedText);
+
+            // Build Expected with flags and deflated data
+            let expectedData = new Uint8Array(4 + deflatedData.length);
+            expectedData[0] = 0x10; // The client capabilities
+            expectedData[1] = 0x00; // Reserved flags
+            expectedData[2] = 0x00; // Reserved flags
+            expectedData[3] = 0x01; // The formats client supports
+            expectedData.set(deflatedData, 4);
+
+            RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
+            expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+            expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
+
+        });
+
+        describe('End of line characters', function () {
+            it('Carriage return', function () {
+
+                let testText = "Hello\rworld\r\r!";
+                let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
+
+                let deflatedData =  deflateWithSize(expectedText);
+
+                // Build Expected with flags and deflated data
+                let expectedData = new Uint8Array(4 + deflatedData.length);
+                expectedData[0] = 0x10; // The client capabilities
+                expectedData[1] = 0x00; // Reserved flags
+                expectedData[2] = 0x00; // Reserved flags
+                expectedData[3] = 0x01; // The formats client supports
+                expectedData.set(deflatedData, 4);
+
+                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
+                expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+                expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
+            });
+
+            it('Carriage return Line feed', function () {
+
+                let testText = "Hello\r\n\r\nworld\r\n!";
+                let expectedText = encodeUTF8(testText + "\0");
+
+                let deflatedData =  deflateWithSize(expectedText);
+
+                // Build Expected with flags and deflated data
+                let expectedData = new Uint8Array(4 + deflatedData.length);
+                expectedData[0] = 0x10; // The client capabilities
+                expectedData[1] = 0x00; // Reserved flags
+                expectedData[2] = 0x00; // Reserved flags
+                expectedData[3] = 0x01; // The formats client supports
+                expectedData.set(deflatedData, 4);
+
+                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
+                expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+                expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
+            });
+
+            it('Line feed', function () {
+                let testText = "Hello\n\n\nworld\n!";
+                let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
+
+                let deflatedData =  deflateWithSize(expectedText);
+
+                // Build Expected with flags and deflated data
+                let expectedData = new Uint8Array(4 + deflatedData.length);
+                expectedData[0] = 0x10; // The client capabilities
+                expectedData[1] = 0x00; // Reserved flags
+                expectedData[2] = 0x00; // Reserved flags
+                expectedData[3] = 0x01; // The formats client supports
+                expectedData.set(deflatedData, 4);
+
+                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
+                expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+                expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
+            });
+
+            it('Carriage return and Line feed mixed', function () {
+                let testText = "\rHello\r\n\rworld\n\n!";
+                let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
+
+                let deflatedData =  deflateWithSize(expectedText);
+
+                // Build Expected with flags and deflated data
+                let expectedData = new Uint8Array(4 + deflatedData.length);
+                expectedData[0] = 0x10; // The client capabilities
+                expectedData[1] = 0x00; // Reserved flags
+                expectedData[2] = 0x00; // Reserved flags
+                expectedData[3] = 0x01; // The formats client supports
+                expectedData.set(deflatedData, 4);
+
+                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
+                expect(RFB.messages.clientCutText).to.have.been.calledOnce;
+                expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
+            });
+        });
+    });
 });