import numpy as np
import polars as pl
import pytest
from qslib import PlateSetup
from qslib.plate_setup import (
Sample,
_color_to_str,
)
def test_plate_setup_equality():
ps = PlateSetup(
{
"s1": ["A1"],
"s2": ["A2", "B4"],
}
)
ps2 = PlateSetup(
{
"s1": ["A1"],
"s2": ["A2", "B4"],
}
)
assert ps == ps2
def test_plate_setup_empty():
ps = PlateSetup()
assert len(ps.samples_by_name) == 0
assert ps.plate_type == 96
def test_plate_setup_dict_construction():
ps = PlateSetup({"sample1": ["A1", "A2"], "sample2": "B1"})
assert "sample1" in ps.samples_by_name
assert "sample2" in ps.samples_by_name
assert ps.samples_by_name["sample1"].wells == ["A1", "A2"]
assert ps.samples_by_name["sample2"].wells == ["B1"]
def test_plate_setup_384():
ps = PlateSetup({"s1": ["A1"]}, plate_type=384)
assert ps.plate_type == 384
def test_get_wells_by_sample():
ps = PlateSetup({"s1": ["A1", "A2"], "s2": ["B1"]})
assert ps.get_wells("s1") == ["A1", "A2"]
def test_get_wells_by_well():
ps = PlateSetup({"s1": ["A1"]})
assert ps.get_wells("A1") == ["A1"]
def test_get_wells_mixed():
ps = PlateSetup({"s1": ["A1", "A2"]})
result = ps.get_wells(["s1", "B1"])
assert result == ["A1", "A2", "B1"]
def test_get_wells_case_insensitive():
ps = PlateSetup({"s1": ["A1"]})
assert ps.get_wells("a1") == ["A1"]
def test_get_descriptive_string_sample():
s = Sample("MySample", description="My Description")
ps = PlateSetup(samples=[s])
ps.sample_wells["MySample"] = ["A1"]
assert ps.get_descriptive_string("MySample") == "My Description"
def test_get_descriptive_string_no_description():
ps = PlateSetup({"MySample": ["A1"]})
assert ps.get_descriptive_string("MySample") == "MySample"
def test_get_descriptive_string_well():
ps = PlateSetup({"s1": ["A1"]})
assert ps.get_descriptive_string("a1") == "A1"
def test_sample_names_as_array():
ps = PlateSetup({"s1": ["A1", "A2"]})
arr = ps.sample_names_as_array()
assert arr.shape == (8, 12)
assert arr[0, 0] == "s1"
assert arr[0, 1] == "s1"
assert arr[1, 0] is None or arr[1, 0] == "None" or str(arr[1, 0]) == "None"
def test_to_polars():
ps = PlateSetup({"s1": ["A1"], "s2": ["B1"]})
df = ps.to_polars()
assert isinstance(df, pl.DataFrame)
assert "name" in df.columns
assert "wells" in df.columns
assert df.height == 2
def test_to_polars_by_well():
ps = PlateSetup({"s1": ["A1"]})
df = ps.to_polars_by_well()
assert df.height == 96 assert "well" in df.columns
assert "name" in df.columns
def test_to_polars_by_well_full():
ps = PlateSetup({"s1": ["A1"]})
df = ps.to_polars_by_well(full=True)
assert df.height == 96
assert "color" in df.columns
def test_to_table_markdown():
ps = PlateSetup({"s1": ["A1"]})
table = ps.to_table(format="markdown")
assert isinstance(table, str)
assert "s1" in table
def test_from_array_96():
arr = np.full((8, 12), None, dtype=object)
arr[0, 0] = "s1"
arr[0, 1] = "s1"
arr[1, 0] = "s2"
ps = PlateSetup.from_array(arr)
assert ps.plate_type == 96
assert ps.samples_by_name["s1"].wells == ["A1", "A2"]
assert ps.samples_by_name["s2"].wells == ["B1"]
def test_from_array_invalid_shape():
arr = np.full((3, 3), None, dtype=object)
with pytest.raises(ValueError, match="must be"):
PlateSetup.from_array(arr)
def test_sample_creation():
s = Sample("TestSample")
assert s.name == "TestSample"
assert s.uuid is not None
assert len(s.uuid) > 0
def test_sample_auto_uuid():
s1 = Sample("S1")
s2 = Sample("S2")
assert s1.uuid != s2.uuid
def test_sample_equality_ignores_uuid():
s1 = Sample("S1", color=(255, 0, 0, 255))
s2 = Sample("S1", color=(255, 0, 0, 255))
assert s1 == s2
def test_sample_inequality():
s1 = Sample("S1")
s2 = Sample("S2")
assert s1 != s2
def test_sample_with_wells():
s = Sample("TestSample", color=(10, 20, 30, 255), description="A test", wells=["A1", "A2"])
assert s.name == "TestSample"
assert s.color == (10, 20, 30, 255)
assert s.description == "A test"
assert s.wells == ["A1", "A2"]
def test_sample_to_record():
s = Sample("TestSample", color=(255, 0, 0, 255))
r = s.to_record()
assert r["name"] == "TestSample"
assert r["color"] == "#ff0000ff"
assert "uuid" in r
def test_color_to_str():
assert _color_to_str((255, 0, 128, 255)) == "#ff0080ff"
def test_plate_setup_xml_roundtrip():
ps = PlateSetup(
{"s1": ["A1", "A2"], "s2": ["B1"]},
samples=[
Sample("s1", color=(255, 0, 0, 255), description="Sample one"),
Sample("s2", color=(0, 255, 0, 255)),
],
)
xml = ps.to_xml_string()
ps2 = PlateSetup.from_xml_string(xml)
assert ps2.plate_type == 96
assert set(ps2.samples_by_name.keys()) == {"s1", "s2"}
assert ps2.samples_by_name["s1"].wells == ["A1", "A2"]
assert ps2.samples_by_name["s2"].wells == ["B1"]
assert ps2.samples_by_name["s1"].color == (255, 0, 0, 255)
assert ps2.samples_by_name["s1"].description == "Sample one"
def test_plate_setup_xml_roundtrip_384():
ps = PlateSetup({"s1": ["A1", "A24"]}, plate_type=384)
xml = ps.to_xml_string()
ps2 = PlateSetup.from_xml_string(xml)
assert ps2.plate_type == 384
assert ps2.samples_by_name["s1"].wells == ["A1", "A24"]
def test_plate_setup_to_xml_string_with_existing():
existing_xml = """<Plate>
<Name>My Plate</Name>
<BarCode>BC123</BarCode>
<Description>Test Description</Description>
<Rows>8</Rows>
<Columns>12</Columns>
<PlateKind>
<Name>96-Well Plate (8x12)</Name>
<Type>TYPE_8X12</Type>
<RowCount>8</RowCount>
<ColumnCount>12</ColumnCount>
</PlateKind>
<FeatureMap>
<Feature>
<Id>sample</Id>
<Name>sample</Name>
</Feature>
</FeatureMap>
</Plate>"""
ps = PlateSetup({"NewSample": ["A1"]})
xml_out = ps.to_xml_string(existing_xml)
ps2 = PlateSetup.from_xml_string(xml_out)
assert "NewSample" in ps2.samples_by_name
assert ps2.samples_by_name["NewSample"].wells == ["A1"]
def test_plate_setup_from_xml_string_bytes():
ps = PlateSetup({"s1": ["A1"]})
xml = ps.to_xml_string()
xml_bytes = xml.encode("utf-8")
ps2 = PlateSetup.from_xml_string(xml_bytes)
assert "s1" in ps2.samples_by_name