Files
ghostgram/submodules/TextFormat/Tests/InstantPageButtonLinkStyleTests.swift
ichmagmaus111andClaude Opus 5.5 0622057a4f Ghostgram: open-source release
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:57:09 +01:00

127 lines
6.0 KiB
Swift

import XCTest
import Postbox
import FlatBuffers
// The flatc-generated TelegramCore_* types live in this module, not in TelegramCore.
import FlatSerialization
import TelegramApi
import TelegramCore
/// `richButtonStyle#3c610bd flags:# bg_primary:flags.0?true bg_danger:flags.1?true
/// bg_success:flags.2?true link:flags.3?true`. `link` is stored as its own field rather than as a
/// fourth `ReplyMarkupButton.Style.Color` case, so `link` + a background bit round-trips losslessly
/// even though rendering makes `link` win.
final class InstantPageButtonLinkStyleTests: XCTestCase {
private func postboxRoundTrip(_ button: InstantPageButton) -> InstantPageButton {
let encoder = PostboxEncoder()
button.encode(encoder)
return InstantPageButton(decoder: PostboxDecoder(buffer: MemoryBuffer(data: encoder.makeData())))
}
private func flatBuffersRoundTrip(_ button: InstantPageButton) throws -> InstantPageButton {
var builder = FlatBufferBuilder(initialSize: 1024)
let offset = button.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset)
var byteBuffer = ByteBuffer(data: builder.data)
let object: TelegramCore_InstantPageButton = FlatBuffers_getRoot(byteBuffer: &byteBuffer)
return try InstantPageButton(flatBuffersObject: object)
}
private func makeButton(color: ReplyMarkupButton.Style.Color?, isLink: Bool) -> InstantPageButton {
return InstantPageButton(text: .plain("Go"), action: .url("https://t.me"), color: color, isLink: isLink)
}
// MARK: - Storage
func testIsLinkSurvivesBothCodecs() throws {
let button = makeButton(color: nil, isLink: true)
XCTAssertTrue(self.postboxRoundTrip(button).isLink)
XCTAssertTrue(try self.flatBuffersRoundTrip(button).isLink)
}
func testNotLinkSurvivesBothCodecs() throws {
let button = makeButton(color: nil, isLink: false)
XCTAssertFalse(self.postboxRoundTrip(button).isLink)
XCTAssertFalse(try self.flatBuffersRoundTrip(button).isLink)
}
/// The point of storing the two facts separately: rendering makes `link` win, but the danger bit
/// must still come back out.
func testLinkAndBackgroundColourBothRoundTrip() throws {
let button = makeButton(color: .danger, isLink: true)
let viaPostbox = self.postboxRoundTrip(button)
XCTAssertTrue(viaPostbox.isLink)
XCTAssertEqual(viaPostbox.color, .danger)
let viaFlatBuffers = try self.flatBuffersRoundTrip(button)
XCTAssertTrue(viaFlatBuffers.isLink)
XCTAssertEqual(viaFlatBuffers.color, .danger)
}
/// Synthesised `==` must pick the new field up, or a page whose only change is the link bit
/// would compare equal and never re-render.
func testEqualityDistinguishesLinkStyle() {
XCTAssertNotEqual(makeButton(color: nil, isLink: true), makeButton(color: nil, isLink: false))
}
// MARK: - Backward compatibility
/// A cached page written before this field existed. Replicates exactly what the old encoder
/// wrote — text object, action, colour — and deliberately omits the "l" key.
func testLegacyPostboxRecordDecodesAsNotLink() {
let encoder = PostboxEncoder()
encoder.encodeObject(RichText.plain("Go"), forKey: "t")
ReplyMarkupButtonAction.url("https://t.me").encode(encoder)
encoder.encodeInt32(ReplyMarkupButton.Style.Color.danger.rawValue, forKey: "c")
let decoded = InstantPageButton(decoder: PostboxDecoder(buffer: MemoryBuffer(data: encoder.makeData())))
XCTAssertFalse(decoded.isLink)
XCTAssertEqual(decoded.color, .danger)
}
/// Same for FlatBuffers: a buffer built without the new field must read the schema default.
func testLegacyFlatBuffersRecordDecodesAsNotLink() throws {
var builder = FlatBufferBuilder(initialSize: 1024)
let textOffset = RichText.plain("Go").encodeToFlatBuffers(builder: &builder)
let actionOffset = ReplyMarkupButtonAction.url("https://t.me").encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageButton.startInstantPageButton(&builder)
TelegramCore_InstantPageButton.add(text: textOffset, &builder)
TelegramCore_InstantPageButton.add(action: actionOffset, &builder)
TelegramCore_InstantPageButton.add(color: -1, &builder)
// No add(isLink:) — this is an old buffer.
let offset = TelegramCore_InstantPageButton.endInstantPageButton(&builder, start: start)
builder.finish(offset: offset)
var byteBuffer = ByteBuffer(data: builder.data)
let object: TelegramCore_InstantPageButton = FlatBuffers_getRoot(byteBuffer: &byteBuffer)
XCTAssertFalse(try InstantPageButton(flatBuffersObject: object).isLink)
}
// MARK: - Api bit mapping, incoming
func testIsLinkStyleReadsBitThree() {
func style(_ flags: Int32) -> Api.RichButtonStyle {
return .richButtonStyle(Api.RichButtonStyle.Cons_richButtonStyle(flags: flags))
}
XCTAssertTrue(InstantPageButton.isLinkStyle(style(1 << 3)))
XCTAssertTrue(InstantPageButton.isLinkStyle(style((1 << 1) | (1 << 3))))
XCTAssertFalse(InstantPageButton.isLinkStyle(style(0)))
XCTAssertFalse(InstantPageButton.isLinkStyle(style(1 << 1)))
XCTAssertFalse(InstantPageButton.isLinkStyle(nil))
}
// MARK: - Api bit mapping, outgoing
/// The trap: the old `apiFlagsAndStyle()` early-returned `(0, nil)` whenever `color` was nil, so
/// a link-only button would have serialised with NO style object at all and lost the bit.
func testLinkOnlyButtonStillEmitsAStyleWord() {
XCTAssertEqual(makeButton(color: nil, isLink: true).apiRichStyleFlagWord, 1 << 3)
}
func testFlagWordCombinesColourAndLink() {
XCTAssertEqual(makeButton(color: .danger, isLink: true).apiRichStyleFlagWord, (1 << 1) | (1 << 3))
XCTAssertEqual(makeButton(color: .danger, isLink: false).apiRichStyleFlagWord, 1 << 1)
XCTAssertEqual(makeButton(color: nil, isLink: false).apiRichStyleFlagWord, 0)
}
}