mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-21 22:34:21 +00:00
📔 Complete?
This commit is contained in:
parent
ee19cfcd63
commit
2856fd0d57
8 changed files with 465 additions and 38 deletions
17
docs/source/3_dev/2_structure/2_frontend/1_techstack.rst
Normal file
17
docs/source/3_dev/2_structure/2_frontend/1_techstack.rst
Normal file
|
@ -0,0 +1,17 @@
|
|||
Librerie e tecnologie utilizzate
|
||||
--------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
Sono elencate solo le principali librerie utilizzate; dipendenze e librerie minori non sono specificate, ma sono visibili all'interno del file ``yarn.lock``.
|
||||
|
||||
- I linguaggi di programmazione `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/About_JavaScript>`_ e `TypeScript <https://www.typescriptlang.org/>`_
|
||||
- Il gestore di dipendenze `Yarn <https://yarnpkg.com/>`_
|
||||
- La libreria grafica `Bluelib <https://github.com/Steffo99/bluelib>`_ (sviluppata come progetto personale nell'estate 2021)
|
||||
- Il framework per interfacce grafiche `React <https://reactjs.org>`_
|
||||
- Il router `Reach Router <https://reach.tech/router/>`_
|
||||
- L'integrazione con React di Bluelib `bluelib-react <https://github.com/Steffo99/bluelib-react>`_ (sviluppata durante il tirocinio)
|
||||
- Il componente React `react-markdown <https://github.com/remarkjs/react-markdown>`_
|
||||
- Il framework per testing `Jest <https://jestjs.io/>`_
|
||||
- Un fork personalizzato del client XHR `axios <https://github.com/axios/axios>`_
|
||||
- Il webserver statico `serve <https://www.npmjs.com/package/serve>`_
|
23
docs/source/3_dev/2_structure/2_frontend/2_tree.rst
Normal file
23
docs/source/3_dev/2_structure/2_frontend/2_tree.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
Struttura delle directory
|
||||
-------------------------
|
||||
.. default-domain:: js
|
||||
|
||||
Le directory di :mod:`@steffo45/sophon-frontend` sono strutturate nella seguente maniera:
|
||||
|
||||
src/components
|
||||
Contiene i componenti React sia con le classi sia funzionali.
|
||||
|
||||
src/contexts
|
||||
Contiene i contesti React creati con :func:`React.createContext`.
|
||||
|
||||
src/hooks
|
||||
Contiene gli hook React personalizzati utilizzati nei componenti funzionali.
|
||||
|
||||
src/types
|
||||
Contiene estensioni ai tipi base TypeScript, come ad esempio i tipi restituiti dalla web API del :ref:`modulo backend`.
|
||||
|
||||
src/utils
|
||||
Contiene varie funzioni di utility.
|
||||
|
||||
public
|
||||
Contiene i file statici da servire assieme all'app.
|
113
docs/source/3_dev/2_structure/2_frontend/3_resources.rst
Normal file
113
docs/source/3_dev/2_structure/2_frontend/3_resources.rst
Normal file
|
@ -0,0 +1,113 @@
|
|||
Comunicazione con il server
|
||||
---------------------------
|
||||
.. default-domain:: js
|
||||
|
||||
|
||||
Axios
|
||||
^^^^^
|
||||
|
||||
Per effettuare richieste all'API web, si è deciso di utilizzare la libreria :mod:`axios`, in quanto permette di creare dei "client" personalizzabili con varie proprietà.
|
||||
|
||||
In particolare, si è scelto di forkarla, integrando anticipatamente una proposta di funzionalità che permette alle richieste di essere interrotte attraverso degli :class:`AbortController`.
|
||||
|
||||
|
||||
Client personalizzati
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Per permettere all'utente di selezionare l'istanza da utilizzare e di comunicare con l'API con le proprie credenziali, si è scelto di creare client personalizzati partendo da due contesti.
|
||||
|
||||
All'interno di un contesto in cui è stata selezionata un'istanza (:data:`InstanceContext`), viene creato un client dal seguente hook:
|
||||
|
||||
.. function:: useInstanceAxios(config = {})
|
||||
|
||||
Questo hook specifica il ``baseURL`` del client Axios, impostandolo all'URL dell'istanza selezionata.
|
||||
|
||||
All'interno di un contesto in cui è stato effettuato l'accesso come utente (:data:`AuthorizationContext`), viene creato invece un client dal seguente hook:
|
||||
|
||||
.. function:: useAuthorizedAxios(config = {})
|
||||
|
||||
Questo hook specifica il valore dell'header ``Authorization`` da inviare in tutte le richieste effettuate a :samp:`Bearer {TOKEN}`, utilizzando il token ottenuto al momento dell'accesso.
|
||||
|
||||
|
||||
Utilizzo di viewset
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Viene implementato un hook che si integra con i viewset di Django, fornendo un API semplificato per effettuare azioni su di essi.
|
||||
|
||||
.. function:: useViewSet(baseRoute)
|
||||
|
||||
Questo hook implementa tutte le azioni :py:mod:`rest_framework` di un viewset in lettura e scrittura.
|
||||
|
||||
Richiede di essere chiamato all'interno di un :data:`AuthorizationContext`.
|
||||
|
||||
.. function:: async list(config = {})
|
||||
.. function:: async retrieve(pk, config = {})
|
||||
.. function:: async create(config)
|
||||
.. function:: async update(pk, config)
|
||||
.. function:: async destroy(pk, config)
|
||||
|
||||
Viene inoltre fornito supporto per le azioni personalizzate.
|
||||
|
||||
.. function:: async command(config)
|
||||
|
||||
Permette azioni personalizzate su tutto il viewset.
|
||||
|
||||
.. function:: async action(config)
|
||||
|
||||
Permette azioni personalizzate su uno specifico oggetto del viewset.
|
||||
|
||||
|
||||
Emulazione di viewset
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Viene creato un hook che tiene traccia degli oggetti restituiti da un determinato viewset, ed emula i risultati delle azioni effettuate, minimizzando i rerender e ottenendo una ottima user experience.
|
||||
|
||||
.. function:: useManagedViewSet(baseRoute, pkKey, refreshOnMount)
|
||||
|
||||
.. attribute:: viewset
|
||||
|
||||
Il viewset restituito da :func:`useViewSet`, utilizzato come interfaccia di basso livello per effettuare azioni.
|
||||
|
||||
.. attribute:: state
|
||||
|
||||
Lo stato del viewset, che tiene traccia degli oggetti e delle azioni in corso su di essi.
|
||||
|
||||
Gli oggetti all'interno di esso sono istanze di :class:`ManagedResource`, create usando wrapper di :func:`.update`, :func:`.destroy` e :func:`.action`, che permettono di modificare direttamente l'oggetto senza preoccuparsi dell'indice a cui si trova nell'array.
|
||||
|
||||
.. attribute:: dispatch
|
||||
|
||||
Riduttore che permette di alterare lo :attr:`.state`.
|
||||
|
||||
.. function:: async refresh()
|
||||
|
||||
Ricarica gli oggetti del viewset.
|
||||
|
||||
Viene chiamata automaticamente al primo render se ``refreshOnMount`` è :data:`True`.
|
||||
|
||||
.. function:: async create(data)
|
||||
|
||||
Crea un nuovo oggetto nel viewset con i dati specificati come argomento, e lo aggiunge allo stato se la richiesta va a buon fine.
|
||||
|
||||
.. function:: async command(method, cmd, data)
|
||||
|
||||
Esegue l'azione personalizzata ``cmd`` su tutto il viewset, utilizzando il metodo ``method`` e con i dati specificati in ``data``.
|
||||
|
||||
Se la richiesta va a buon fine, il valore restituito dal backend sostituisce nello stato le risorse dell'intero viewset.
|
||||
|
||||
.. function:: async update(index, data)
|
||||
|
||||
Modifica l'oggetto alla posizione ``index`` dell'array :attr:`.state` con i dati specificati in ``data``.
|
||||
|
||||
Se la richiesta va a buon fine, la modifica viene anche applicata all'interno di :attr:`.state`
|
||||
|
||||
.. function:: async destroy(index)
|
||||
|
||||
Elimina l'oggetto alla posizione ``index`` dell'array :attr:`.state`.
|
||||
|
||||
Se la richiesta va a buon fine, l'oggetto viene eliminato anche da :attr:`.state`.
|
||||
|
||||
.. function:: async action(index, method, act, data)
|
||||
|
||||
Esegue l'azione personalizzata ``act`` sull'oggetto alla posizione ``index`` dell'array :attr:`.state`, utilizzando il metodo ``method`` e con i dati specificati in ``data``.
|
||||
|
||||
Se la richiesta va a buon fine, il valore restituito dal backend sostituisce l'oggetto utilizzato in :attr:`.state`.
|
161
docs/source/3_dev/2_structure/2_frontend/4_contexts.rst
Normal file
161
docs/source/3_dev/2_structure/2_frontend/4_contexts.rst
Normal file
|
@ -0,0 +1,161 @@
|
|||
Contesti innestati
|
||||
------------------
|
||||
|
||||
Per minimizzare i rerender, l'applicazione è organizzata a "contesti innestati".
|
||||
|
||||
|
||||
I contesti
|
||||
^^^^^^^^^^
|
||||
|
||||
Viene definito un contesto per ogni tipo di risorsa selezionabile nell'interfaccia.
|
||||
|
||||
Essi sono, in ordine dal più esterno al più interno:
|
||||
|
||||
#. :data:`InstanceContext` (:ref:`Istanza`)
|
||||
#. :data:`AuthorizationContext` (:ref:`Utente`)
|
||||
#. :data:`GroupContext` (:ref:`Gruppo di ricerca`)
|
||||
#. :data:`ProjectContext` (:ref:`Progetto di ricerca`)
|
||||
#. :data:`NotebookContext` (:ref:`Notebook`)
|
||||
|
||||
|
||||
Contenuto dei contesti
|
||||
""""""""""""""""""""""
|
||||
|
||||
Questi contesti possono avere tre tipi di valori: :data:`undefined` se ci si trova al di fuori del contesto, :data:`null` se non è stato selezionato alcun oggetto oppure **l'oggetto selezionato** se esso esiste.
|
||||
|
||||
|
||||
URL contestuale
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Si è definita la seguente struttura per gli URL del frontend di Sophon, in modo che essi identificassero universalmente una risorsa e che essi fossero human-readable.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
/i/{ISTANZA}
|
||||
/l/logged-in
|
||||
/g/{GROUP_SLUG}
|
||||
/p/{PROJECT_SLUG}
|
||||
/n/{NOTEBOOK_SLUG}/
|
||||
|
||||
Ad esempio, l'URL per il notebook ``my-first-notebook`` dell'istanza demo di Sophon sarebbe:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
/i/https:api.prod.sophon.steffo.eu:
|
||||
/l/logged-in
|
||||
/g/my-first-group
|
||||
/p/my-first-project
|
||||
/n/my-first-notebook/
|
||||
|
||||
|
||||
Parsing degli URL contestuali
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Viene definita una funzione in grado di comprendere gli URL contestuali:
|
||||
|
||||
.. function:: parsePath(path)
|
||||
|
||||
:param path: Il "path" da leggere.
|
||||
:returns:
|
||||
Un oggetto con le seguenti chiavi, dette "segmenti di percorso", le quali possono essere :data:`undefined` per indicare che non è stato selezionato un oggetto di quel tipo:
|
||||
|
||||
- ``instance``: l'URL dell'istanza da utilizzare, con caratteri speciali sostituiti da ``:``
|
||||
- ``loggedIn``: :class:`Boolean`, se :data:`True` l'utente ha effettuato il login (come :ref:`Ospite` o :ref:`Utente`)
|
||||
- ``researchGroup``: lo slug del :ref:`gruppo di ricerca` selezionato
|
||||
- ``researchProject``: lo slug del :ref:`progetto di ricerca` selezionato
|
||||
- ``notebook``: lo slug del :ref:`notebook` selezionato
|
||||
|
||||
Ad esempio, l'URL precedente restituirebbe il seguente oggetto se processato:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
{
|
||||
"instance": "https:api.prod.sophon.steffo.eu:",
|
||||
"loggedIn": True,
|
||||
"researchGroup": "my-first-group",
|
||||
"researchProject": "my-first-project",
|
||||
"notebook": "my-first-notebook"
|
||||
}
|
||||
|
||||
|
||||
Routing basato sui contesti
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
I valori dei contesti vengono utilizzati per selezionare i componenti da mostrare all'utente nell'interfaccia grafica attraverso i seguenti componenti:
|
||||
|
||||
.. function:: ResourceRouter({selection, unselectedRoute, selectedRoute})
|
||||
|
||||
Componente che sceglie se renderizzare ``unselectedRoute`` o ``selectedRoute`` in base alla *nullità* o *non-nullità* di ``selection``.
|
||||
|
||||
.. function:: ViewSetRouter({viewSet, unselectedRoute, selectedRoute, pathSegment, pkKey})
|
||||
|
||||
Componente basato su :func:`ResourceRouter` che seleziona automaticamente l'elemento del viewset avente il valore del segmento di percorso ``pathSegment`` alla chiave ``pkKey``.
|
||||
|
||||
|
||||
Esempio di utilizzo di ViewSetRouter
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
|
||||
.. function:: GroupRouter({...props})
|
||||
|
||||
Implementato come:
|
||||
|
||||
.. code-block:: tsx
|
||||
|
||||
<ViewSetRouter
|
||||
{...props}
|
||||
viewSet={useManagedViewSet<SophonResearchGroup>("/api/core/groups/", "slug")}
|
||||
pathSegment={"researchGroup"}
|
||||
pkKey={"slug"}
|
||||
/>
|
||||
|
||||
|
||||
Albero completo dei contesti
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
L'insieme di tutti i contesti è definito come componente :func:`App` nel modulo "principale" ``App.tsx``.
|
||||
|
||||
Se ne riassume la struttura in pseudocodice:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<InstanceContext>
|
||||
<InstanceRouter>
|
||||
unselected:
|
||||
<InstanceSelect>
|
||||
selected:
|
||||
<AuthorizationContext>
|
||||
<AuthorizationRouter>
|
||||
unselected:
|
||||
<UserLogin>
|
||||
selected:
|
||||
<GroupContext>
|
||||
<GroupRouter>
|
||||
unselected:
|
||||
<GroupSelect>
|
||||
selected:
|
||||
<ProjectContext>
|
||||
<ProjectRouter>
|
||||
unselected:
|
||||
<ProjectSelect>
|
||||
selected:
|
||||
<NotebookContext>
|
||||
<NotebookRouter>
|
||||
unselected:
|
||||
<NotebookSelect>
|
||||
selected:
|
||||
<NotebookDetails>
|
||||
|
||||
|
||||
Altri contesti
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Tema
|
||||
""""
|
||||
|
||||
Il tema dell'istanza è implementato come uno speciale contesto globale :data:`ThemeContext` che riceve i dettagli dell'istanza a cui si è collegati dall':data:`InstanceContext`.
|
||||
|
||||
|
||||
Cache
|
||||
"""""
|
||||
|
||||
Viene salvato l'elenco di tutti i membri dell':ref:`istanza` in uno speciale contesto :data:`CacheContext` in modo da poter risolvere gli id degli utenti al loro username senza dover effettuare ulteriori richieste.
|
|
@ -8,28 +8,12 @@ Si è cercato di renderla più user-friendly possibile, cercando di comunicare p
|
|||
|
||||
È collocato all'interno del repository in ``/frontend``.
|
||||
|
||||
Il modulo è formato dal package JavaScript :mod:`@steffo45/sophon-frontend`, che contiene tutti i componenti React che assemblati insieme formano l'intera interfaccia web.
|
||||
|
||||
Librerie e tecnologie utilizzate
|
||||
--------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
Sono elencate solo le principali librerie utilizzate; dipendenze e librerie minori non sono specificate, ma sono visibili all'interno del file ``yarn.lock``.
|
||||
|
||||
- I linguaggi di programmazione `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/About_JavaScript>`_ e `TypeScript <https://www.typescriptlang.org/>`_
|
||||
- Il gestore di dipendenze `Yarn <https://yarnpkg.com/>`_
|
||||
- La libreria grafica `Bluelib <https://github.com/Steffo99/bluelib>`_ (sviluppata come progetto personale nell'estate 2021)
|
||||
- Il framework per interfacce grafiche `React <https://reactjs.org>`_
|
||||
- Il router `Reach Router <https://reach.tech/router/>`_
|
||||
- L'integrazione con React di Bluelib `bluelib-react <https://github.com/Steffo99/bluelib-react>`_ (sviluppata durante il tirocinio)
|
||||
- Il componente React `react-markdown <https://github.com/remarkjs/react-markdown>`_
|
||||
- Il framework per testing `Jest <https://jestjs.io/>`_
|
||||
- Un fork personalizzato del client XHR `axios <https://github.com/axios/axios>`_
|
||||
- Il webserver statico `serve <https://www.npmjs.com/package/serve>`_
|
||||
|
||||
|
||||
Struttura del modulo
|
||||
--------------------
|
||||
|
||||
Il modulo consiste nel package JavaScript :mod:`@steffo45/sophon-frontend`, che contiene tutti i componenti che assemblati insieme formano l'intera interfaccia web.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1_techstack
|
||||
2_tree
|
||||
3_resources
|
||||
4_contexts
|
||||
|
|
|
@ -1,3 +1,132 @@
|
|||
Test effettuati
|
||||
===============
|
||||
|
||||
Per motivi di tempo necessario, sono state selezionate solo alcune parti di codice su cui effettuare test.
|
||||
|
||||
|
||||
Tutti i viewset di :py:mod:`sophon.core`
|
||||
----------------------------------------
|
||||
.. default-domain:: py
|
||||
.. default-role:: py:obj
|
||||
.. module:: sophon.core.tests
|
||||
|
||||
|
||||
Test case generici
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Vengono definiti alcuni test case generici per facilitare le interazioni tra ``APITestCase`` e viewset.
|
||||
|
||||
.. note::
|
||||
|
||||
I nomi delle funzioni usano nomi con capitalizzazione inconsistente in quanto lo stesso modulo `unittest` non rispetta lo stile suggerito in :pep:`8`.
|
||||
|
||||
.. class:: BetterAPITestCase(APITestCase)
|
||||
|
||||
.. method:: as_user(self, username: str, password: str = None) -> typing.ContextManager[None]
|
||||
|
||||
Context manager che permette di effettuare richieste all'API come uno specifico utente, effettuando il logout quando sono state effettuate le richieste necessarie.
|
||||
|
||||
.. method:: assertData(self, data: ReturnDict, expected: dict)
|
||||
|
||||
Asserzione che permette di verificare che l'oggetto restituito da una richiesta all'API contenga almeno le chiavi e i valori contenuti nel dizionario ``expected``.
|
||||
|
||||
.. class:: ReadSophonTestCase(BetterAPITestCase, metaclass=abc.ABCMeta)
|
||||
|
||||
Classe **astratta** che implementa metodi per testare rapidamente le azioni di un `.views.ReadSophonViewSet`.
|
||||
|
||||
.. classmethod:: get_basename(cls) -> str
|
||||
|
||||
Metodo **astratto** che deve restituire il basename del viewset da testare.
|
||||
|
||||
.. classmethod:: get_url(cls, kind: str, *args, **kwargs) -> str
|
||||
|
||||
Metodo utilizzato dal test case per trovare gli URL ai quali possono essere effettuate le varie azioni.
|
||||
|
||||
I seguenti metodi permettono di effettuare azioni sul viewset:
|
||||
|
||||
.. method:: list(self) -> rest_framework.response.Response
|
||||
.. method:: retrieve(self, pk) -> rest_framework.response.Response
|
||||
.. method:: custom_list(self, method: str, action: str, data: dict = None) -> rest_framework.response.Response
|
||||
.. method:: custom_detail(self, method: str, action: str, pk, data: dict = None) -> rest_framework.response.Response
|
||||
|
||||
I seguenti metodi asseriscono che una determinata azione con determinati parametri risponderà con il codice di stato ``code``, e restituiscono i dati contenuti nella risposta se l'azione è riuscita (``200 <= code < 300``)
|
||||
|
||||
.. method:: assertActionList(self, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
.. method:: assertActionRetrieve(self, pk, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
.. method:: assertActionCustomList(self, method: str, action: str, data: dict = None, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
.. method:: assertActionCustomDetail(self, method: str, action: str, pk, data: dict = None, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
|
||||
|
||||
.. class:: WriteSophonTestCase(ReadSophonTestCase, metaclass=abc.ABCMeta)
|
||||
|
||||
Classe **astratta** che estende `.ReadSophonTestCase` con le azioni di un `.views.WriteSophonViewSet`.
|
||||
|
||||
.. method:: create(self, data) -> rest_framework.response.Response
|
||||
.. method:: update(self, pk, data) -> rest_framework.response.Response
|
||||
.. method:: destroy(self, pk) -> rest_framework.response.Response
|
||||
|
||||
.. method:: assertActionCreate(self, data, code: int = 201) -> typing.Optional[ReturnDict]
|
||||
.. method:: assertActionUpdate(self, pk, data, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
.. method:: assertActionDestroy(self, pk, code: int = 200) -> typing.Optional[ReturnDict]
|
||||
|
||||
|
||||
Test case concreti
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Vengono testate tutte le view dell'app tramite `.BetterAPITestCase` e tutti i viewset dell'app tramite `.ReadSophonTestCase` e `WriteSophonTestCase`.
|
||||
|
||||
.. class:: UsersByIdTestCase(ReadSophonTestCase)
|
||||
.. class:: UsersByUsernameTestCase(ReadSophonTestCase)
|
||||
.. class:: ResearchGroupTestCase(WriteSophonTestCase)
|
||||
.. class:: SophonInstanceDetailsTestCase(BetterAPITestCase)
|
||||
|
||||
|
||||
Alcune interazioni di `sophon.notebooks`
|
||||
----------------------------------------
|
||||
.. default-domain:: py
|
||||
.. default-role:: py:obj
|
||||
.. module:: sophon.notebooks.tests
|
||||
|
||||
Vengono definiti alcuni test case per alcune interazioni dell'app `sophon.notebooks`.
|
||||
|
||||
.. class:: JupyterTestCase(TestCase)
|
||||
|
||||
Test case che testa la generazione dei token per Jupyter.
|
||||
|
||||
.. class:: ApacheTestCase(TestCase)
|
||||
|
||||
Test case che testa la conversione in `bytes` per la rubrica `dbm` del :ref:`modulo proxy`.
|
||||
|
||||
|
||||
Alcune interazioni complicate del frontend
|
||||
------------------------------------------
|
||||
.. default-domain:: js
|
||||
.. default-role:: js:class
|
||||
|
||||
Vengono infine definiti test case per alcune interazioni ritenute particolarmente complesse del frontend.
|
||||
|
||||
|
||||
Encoding dell'URL dell'istanza nell'URL della pagina
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- encodes pathless URL
|
||||
- encodes URL with port number
|
||||
- encodes URL with simple path
|
||||
- encodes URL with colon in path
|
||||
- does not encode URL with ``%3A`` in path
|
||||
- decodes pathless URL
|
||||
- decodes URL with port number
|
||||
- decodes URL with simple path
|
||||
- decodes URL with colon in path
|
||||
|
||||
|
||||
Parsing dei segmenti del path
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- parses empty path
|
||||
- parses instance path
|
||||
- parses username path
|
||||
- parses userid path
|
||||
- parses research group path
|
||||
- parses research project path
|
||||
- parses research project path
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
import { parsePath } from "./ParsePath"
|
||||
|
||||
|
||||
test("splits empty path", () => {
|
||||
test("parses empty path", () => {
|
||||
expect(
|
||||
parsePath("/"),
|
||||
).toMatchObject(
|
||||
{}
|
||||
{},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits instance path", () => {
|
||||
test("parses instance path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:"),
|
||||
).toMatchObject(
|
||||
{
|
||||
instance: "https:api:sophon:steffo:eu:"
|
||||
}
|
||||
instance: "https:api:sophon:steffo:eu:",
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits username path", () => {
|
||||
test("parses username path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:/u/steffo"),
|
||||
).toMatchObject(
|
||||
{
|
||||
instance: "https:api:sophon:steffo:eu:",
|
||||
userName: "steffo",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits userid path", () => {
|
||||
test("parses userid path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:/u/1"),
|
||||
).toMatchObject(
|
||||
{
|
||||
instance: "https:api:sophon:steffo:eu:",
|
||||
userId: "1",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits research group path", () => {
|
||||
test("parses research group path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers"),
|
||||
).toMatchObject(
|
||||
{
|
||||
instance: "https:api:sophon:steffo:eu:",
|
||||
researchGroup: "testers",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits research project path", () => {
|
||||
test("parses research project path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test"),
|
||||
).toMatchObject(
|
||||
|
@ -60,11 +60,11 @@ test("splits research project path", () => {
|
|||
instance: "https:api:sophon:steffo:eu:",
|
||||
researchGroup: "testers",
|
||||
researchProject: "test",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test("splits research project path", () => {
|
||||
test("parses research project path", () => {
|
||||
expect(
|
||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test/n/testerino"),
|
||||
).toMatchObject(
|
||||
|
@ -73,6 +73,6 @@ test("splits research project path", () => {
|
|||
researchGroup: "testers",
|
||||
researchProject: "test",
|
||||
notebook: "testerino",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue